Search code examples
javafxgluongluon-mobile

How to define gluon application version


I want to put the application version to my gluon application. but I don't know how to do. Do I need to create service for that?

Screenshot


Solution

  • There is a very simple approach, that doesn't required a service, but it can lead to errors:

    You can maintain a version code/version number for Android in the AndroidManifest:

    <?xml version="1.0" encoding="UTF-8"?>
        <manifest xmlns:android="http://schemas.android.com/apk/res/android" 
                  package="your.package" 
                  android:versionCode="1.1.0" 
                  android:versionName="1.1.0">
    

    The same on iOS, in the Default-Info.plist:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
            <key>CFBundleIdentifier</key>
            <string>your.bundle.identifier</string>
            <key>CFBundleVersion</key>
            <string>1.1.0</string>
    

    And finally you can keep a static field in your code:

    public static final String VERSION_NAME = "1.1.0";
    

    that you can access from the NavigationDrawer, for instance.

    However this requires manual updating every time you make a new release.

    To avoid errors, you can set some CI job that does it for you. See for instance this commit, that it is part of the release job of a mobile app.

    Charm Down Service

    However, a more definitive way of avoiding mismatches between the released versions and the variable in your code is by adding a service that directly reads those versions.

    Something like this:

    VersionService.java

    package com.gluonhq.charm.down.plugins;
    
    public interface VersionService {
    
        String getVersionName();
    
    }
    

    VersionServiceFactory.java

    package com.gluonhq.charm.down.plugins;
    
    import com.gluonhq.charm.down.DefaultServiceFactory;
    
    public class VersionServiceFactory extends DefaultServiceFactory<VersionService> {
    
        public VersionServiceFactory() {
            super(VersionService.class);
        }
    
    }
    

    Android package, under src/android/java:

    AndroidVersionService.java

    package com.gluonhq.charm.down.plugins.android;
    
    import android.content.pm.PackageManager;
    import com.gluonhq.charm.down.plugins.VersionService;
    import javafxports.android.FXActivity;
    
    public class AndroidVersionService implements VersionService {
    
        @Override
        public String getVersionName() {
            try {
                return FXActivity.getInstance().getPackageManager()
                        .getPackageInfo(FXActivity.getInstance().getPackageName(), 0)
                        .versionName;
            } catch (PackageManager.NameNotFoundException ex) { }
            return "";
        }
    
    }
    

    IOS package, under src/ios/java:

    IOSVersionService.java

    package com.gluonhq.charm.down.plugins.ios;
    
    import com.gluonhq.charm.down.plugins.VersionService;
    
    public class IOSVersionService implements VersionService {
    
        static {
            System.loadLibrary("Version");
        }
    
        @Override
        public String getVersionName() {
            return getNativeVersion();
        }
    
        private static native String getNativeVersion();
    
    }
    

    Under src/ios/native:

    Version.m

    #import <UIKit/UIKit.h>
    #include "jni.h"
    
    JNIEXPORT jint JNICALL
    JNI_OnLoad_Version(JavaVM *vm, void *reserved)
    {
    #ifdef JNI_VERSION_1_8
        //min. returned JNI_VERSION required by JDK8 for builtin libraries
        JNIEnv *env;
        if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_8) != JNI_OK) {
            return JNI_VERSION_1_4;
        }
        return JNI_VERSION_1_8;
    #else
        return JNI_VERSION_1_4;
    #endif
    }
    
    JNIEXPORT jstring JNICALL Java_com_gluonhq_charm_down_plugins_ios_IOSVersionService_getNativeVersion
    (JNIEnv *env, jclass jClass)
    {
        NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
        return (*env)->NewStringUTF(env, [version cStringUsingEncoding:NSASCIIStringEncoding]);
    }
    

    Build files, at root level:

    ios-build.gradle

    if (System.getProperty('os.name').toLowerCase().contains("mac")) {
        new ByteArrayOutputStream().withStream { os ->
            exec {
                args '-version', '-sdk', 'iphoneos', 'SDKVersion'
                executable 'xcodebuild'
                standardOutput = os
            }
            ext.IOS_VERSION = os.toString().trim()
        }
    } else {
        ext.IOS_VERSION = ""
    }
    ext.IS_DEBUG_NATIVE = Boolean.parseBoolean(System.getProperty("IS_DEBUG_NATIVE", "false"))
    
    def sdkPath(String platform) {
        return "/Applications/Xcode.app/Contents/Developer/Platforms/${platform}.platform/Developer/SDKs/${platform}${IOS_VERSION}.sdk";
    }
    
    ext.xcodebuildIOS = {buildDir, projectDir, name ->
    
        if (!file(sdkPath('iPhoneOS')).exists()) {
            println "Skipping xcodebuild"
            return
        }
    
        // define statics do being able to configure the input/output files on the task
        // for faster builds if nothing changed
        def buildSystems = ["iPhoneOS+arm64",
                            "iPhoneOS+armv7",
                            "iPhoneSimulator+i386",
                            "iPhoneSimulator+x86_64"]
        def linkerOutputs = []
    
        def lipoOutput = "$buildDir/native/lib${name}.a"
    
        def nativeSources = ["$projectDir/src/ios/native/${name}.m"]
    
        // the actual task action
        buildSystems.each { buildSystem ->
    
            def (platform, arch) = buildSystem.tokenize("+");
    
            def compileOutput = "$buildDir/native/$arch"
            def compileOutputs = ["$buildDir/native/$arch/${name}.o"]
    
            def linkerOutput = "$buildDir/native/$arch/lib${name}.a"
    
            new File(compileOutput).mkdirs();
    
            def clangArgs = [
                    "-x", "objective-c",
                    "-miphoneos-version-min=6.0",
                    "-fmessage-length=0",
                    "-std=c99",
                    "-fno-common",
                    "-Wall",
                    "-fno-strict-aliasing",
                    "-fwrapv",
                    "-fpascal-strings",
                    "-fobjc-abi-version=2",
                    "-fobjc-legacy-dispatch",
                    "-I" + System.getenv("JAVA_HOME") + "/include",
                    "-I" + System.getenv("JAVA_HOME") + "/include/darwin",
                    "-c",
                    IS_DEBUG_NATIVE ? ["-O0", "-DDEBUG", "-g"] : ["-O3", "-DNDEBUG"],
                    "-arch", arch,
                    "-isysroot",
                    sdkPath(platform),
                    nativeSources].flatten()
            // "-o", compileOutput,
    
            def linkerArgs = [
                    "-static",
                    "-framework", "Foundation",
                    "-framework", "CoreGraphics",
                    "-framework", "CoreBluetooth",
                    "-framework", "CoreLocation",
                    "-framework", "CoreMotion",
                    "-framework", "CoreText",
                    "-framework", "UIKit",
                    "-framework", "QuartzCore",
                    "-framework", "OpenGLES",
                    "-framework", "UserNotifications",
                    "-arch_only", arch,
                    "-syslibroot", sdkPath(platform),
                    "-L${sdkPath(platform)}/usr/lib",
                    "-o", linkerOutput,
                    compileOutputs
            ].flatten()
    
            // execute compiler
            exec {
                executable "clang"
                args clangArgs
                workingDir compileOutput
            }
    
            // execute linker
            exec {
                executable "libtool"
                args linkerArgs
                workingDir compileOutput
            }
    
            linkerOutputs.add(linkerOutput)
        }
    
        def lipoArgs = [
                "-create",
                linkerOutputs,
                "-o",
                lipoOutput
        ].flatten();
    
        // execute lipo to combine all linker output in one archive
        exec {
            executable "lipo"
            args lipoArgs
        }
    }
    

    build.gradle

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'org.javafxports:jfxmobile-plugin:1.3.16'
        }
    }
    
    apply plugin: 'org.javafxports.jfxmobile'
    apply from: 'ios-build.gradle'
    
    repositories {
        jcenter()
        maven {
            url 'http://nexus.gluonhq.com/nexus/content/repositories/releases'
        }
    }
    
    mainClassName = 'your.main.class'
    
    dependencies {
        compile 'com.gluonhq:charm:5.0.2'
    }
    
    jfxmobile {
        javafxportsVersion = '8.60.11'
        downConfig {
            version = '3.8.6'
            // Do not edit the line below. Use Gluon Mobile Settings in your project context menu instead
            plugins 'display', 'lifecycle', 'statusbar', 'storage'
        }
        android {
            manifest = 'src/android/AndroidManifest.xml'
        }
        ios {
            infoPList = file('src/ios/Default-Info.plist')
            forceLinkClasses = [
                    'com.gluonhq.**.*',
                    'javax.annotations.**.*',
                    'javax.inject.**.*',
                    'javax.json.**.*',
                    'org.glassfish.json.**.*'
            ]
        }
    }
    
    task xcodebuild {
        doLast {
            xcodebuildIOS("$project.buildDir","$project.projectDir", "Version")
        }
    }
    
    task installNativeLib (type:Copy, dependsOn: xcodebuild) {
        from("$project.buildDir/native")
        into("src/ios/jniLibs")
        include("*.a")
    }
    

    Before deploying to iOS, run:

    ./gradlew installNativeLib
    

    Service use

    Finally, you can use the service anywhere in your code:

    String version = Services.get(VersionService.class)
                .map(VersionService::getVersionName)
                .orElse(""));