Search code examples
h2gluonrobovm

Gluon Application can't load FXML on ios when I using H2 database as embedded


I have create Gluon mobile application that using H2 database as embedded mode. It working fine on Window,Mac,Android. But it unluckily it not working on iOS simulator (The fxml can't load).

If I comment the code to load Driver, the UI will load properly.

Code Load Driver

//load the driver class
        try {
            Class.forName(DBCONFIG.DB_DRIVER).newInstance();
        } catch (ClassNotFoundException e) {
            System.out.println("class not found");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            System.out.println("IllegalAccessException");
            e.printStackTrace();
        } catch (InstantiationException e) {
            System.out.println("InstantiationException");
            e.printStackTrace();
        }

I also check discussion on this group, but no luck.

My Code:

private void createDB() {
        status.getItems().add("Creating a Database with H2");
        //Get the private storage directory from each platform to save database files;
        private_dir = GluonServices.getPrivateDirectory();
        System.out.println("path="+private_dir.toString());
        //load the driver class
        try {
            Class.forName(DBCONFIG.DB_DRIVER).newInstance();
        } catch (ClassNotFoundException e) {
            System.out.println("class not found");
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            System.out.println("IllegalAccessException");
            e.printStackTrace();
        } catch (InstantiationException e) {
            System.out.println("InstantiationException");
            e.printStackTrace();
        }
        //Build Database connection string to the correct file that we create depending on each platform;
        dbUrl = String.format(dbUrl,private_dir.toString(),DBCONFIG.DB_NAME);
        System.out.println("dbUrl="+dbUrl);

        List<Contact> list = new ArrayList<>();
        try {
            //Connect to database from the giving db url: If file doesn't exist H2 will automatically create it.
            System.out.println("getConnection=");
            connection = DriverManager.getConnection(dbUrl);
            status.getItems().add("Connection established: " + dbUrl);

            //create the statement object
            stmt = connection.createStatement();
            stmt.setQueryTimeout(30);
            String sql="DROP TABLE IF EXISTS contact;\n" +
                    "CREATE TABLE contact(\n" +
                    "        Id INT NOT NULL AUTO_INCREMENT,\n" +
                    "        Name VARCHAR(255) NOT NULL,\n" +
                    "        Email VARCHAR(255) NOT NULL UNIQUE,\n" +
                    "        PRIMARY KEY (ID)\n" +
                    ");";

            //execute query
            stmt.executeUpdate(sql);
            status.getItems().add("Starting Adding testing data and read it from database");
            stmt.executeUpdate("INSERT INTO CONTACT(NAME,EMAIL) VALUES('Sovandara LENG','[email protected]')");
            stmt.executeUpdate("INSERT INTO CONTACT(NAME,EMAIL) VALUES('Chamroeun PANG','[email protected]')");
            stmt.executeUpdate("INSERT INTO CONTACT(NAME,EMAIL) VALUES('Sovanarith LENG','[email protected]')");

            status.getItems().add("Retrieving records from table 'Contact'...");
            rs = stmt.executeQuery("SELECT * FROM CONTACT");
            while (rs.next()) {
                int id = rs.getInt("Id");
                String name = rs.getString("NAME");
                String email = rs.getString("Email");
                status.getItems().add(String.format("Record Added:{id: %s, name: %s, email: %s}",id,name,email));
                list.add(new Contact(id,name, email));
            }
            status.getItems().add("End creating table and retrieving records");
            if(list.size()>0)
                status.getItems().add("Record has beed added");

            System.out.println("Table Created");
            status.getItems().add("Table Created");

        } catch (Exception e) {
            status.getItems().add("SQL error  " + e.getMessage());
            e.printStackTrace();
        }finally {
            try {
                if (rs != null) {
                    rs.close();
                }
            } catch (SQLException ex) {
                status.getItems().add("SQL error " + ex.getSQLState());
            }
            try {
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException ex) {
                status.getItems().add("SQL error " + ex.getSQLState());
            }
        }
    }

Build.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'org.javafxports:jfxmobile-plugin:1.3.12'
        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.0'
    }
}

apply plugin: 'org.javafxports.jfxmobile'
apply plugin: 'com.github.johnrengelman.shadow'

repositories {
    jcenter()
    maven {
        url 'http://nexus.gluonhq.com/nexus/content/repositories/releases'
    }
}

mainClassName = 'com.khmerdev.GluonApplication'

dependencies {
    compile 'com.gluonhq:charm:5.0.0'
    compile files('libs/h2-1.4.196.jar')
}
// Add desktop dependencies to the task
shadowJar {
    configurations = [project.configurations.desktopRuntime]
}

jfxmobile {
    downConfig {
        version = '3.8.0'
        // 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.khmerdev.**.*',
                'com.gluonhq.**.*',
                'javax.annotations.**.*',
                'javax.inject.**.*',
                'javax.json.**.*',
                'org.glassfish.json.**.*'
        ]
    }
}

Log

3:43:02 PM: Executing task 'launchIPadSimulator'...

The CompileOptions.bootClasspath property has been deprecated and is scheduled to be removed in Gradle 5.0. Please use the CompileOptions.bootstrapClasspath property instead.
Note: /Users/sovandara/Documents/KhmerDEV/Project/cashmag/source_code/contactapp/ContactApp/src/main/java/com/khmerdev/sample/H2app.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: /Users/sovandara/Documents/KhmerDEV/Project/cashmag/source_code/contactapp/ContactApp/src/main/java/com/khmerdev/sample/H2app.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
:ContactApp:compileJava
:ContactApp:processResources UP-TO-DATE
:ContactApp:classes
:ContactApp:createDefaultIOSLauncher UP-TO-DATE
:ContactApp:compileIosJava UP-TO-DATE
:ContactApp:processIosResources NO-SOURCE
:ContactApp:iosClasses UP-TO-DATE
:ContactApp:iosExtractNativeLibs UP-TO-DATE
:ContactApp:launchIPadSimulator
Root pattern javax.annotations.**.* matches no classes
Root pattern javax.inject.**.* matches no classes
javax.xml.stream.XMLEventFactory=com.sun.xml.stream.events.ZephyrEvent...
java.vendor.url=http://www.robovm.org/
java.ext.dirs=
line.separator=

file.encoding=UTF-8
java.runtime.version=0.9
user.name=mobile
java.compiler=
prism.static.libraries=true
android.icu.unicode.version=6.2
javax.xml.stream.XMLOutputFactory=com.sun.xml.stream.ZephyrWriterFactory
java.version=0
android.icu.library.version=51.1.0.1
os.arch=x86_64
java.io.tmpdir=/Users/sovandara/Library/Developer/Co...
glass.platform=ios
android.zlib.version=1.2.8
user.language=en
java.vm.version=2.3.1-ios11
com.sun.javafx.isEmbedded=true
javax.xml.stream.XMLInputFactory=com.sun.xml.stream.ZephyrParserFactory
path.separator=:
java.runtime.name=RoboVM Runtime
java.specification.version=0.9
user.dir=/Users/sovandara/Library/Developer/Co...
java.vm.specification.vendor=RoboVM
java.vm.name=RoboVM
java.vm.specification.version=0.9
prism.useNativeIIO=false
user.home=/Users/sovandara/Library/Developer/Co...
jfxmedia.platforms=IOSPlatform
java.specification.name=RoboVM Core Library
file.separator=/
user.variant=
os.version=10.13.5
java.boot.class.path=/Users/sovandara/Library/Developer/Co...
java.vm.specification.name=RoboVM Virtual Machine Specification
javafx.platform=ios
user.region=US
os.name=iOS Simulator
java.class.path=/Users/sovandara/Library/Developer/Co...
prism.verbose=true
java.specification.vendor=RoboVM
java.vm.vendor=RoboVM
prism.mintexturesize=16
prism.allowhidpi=true
java.vendor=RoboVM
android.icu.cldr.version=23.1
android.openssl.version=OpenSSL 1.0.1e 11 Feb 2013
java.home=/Users/sovandara/Library/Developer/Co...
java.vm.vendor.url=http://www.robovm.org/
java.class.version=50.0
Prism pipeline init order: es2 
Using native-based Pisces rasterizer
Using dirty region optimizations
Using system sized mask for primitives
Not forcing power of 2 sizes for textures
Using hardware CLAMP_TO_ZERO mode
Opting in for HiDPI pixel scaling
Prism pipeline name = com.sun.prism.es2.ES2Pipeline
Loading ES2 native library ... prism_es2
    succeeded.
GLFactory using com.sun.prism.es2.IOSGLFactory
IOSWindowSystemInterface : share 0 view 0 pf otready 
GL_VERSION string = OpenGL ES 2.0 APPLE-16.4.2
GL_VERSION (major.minor) = 0.0
  initialize() returns 140384845988192
(X) Got class = class com.sun.prism.es2.ES2Pipeline
Initialized prism pipeline: com.sun.prism.es2.ES2Pipeline
IOSWindowSystemInterface : share 271e0 view 0 pf otready 
GL_VERSION string = OpenGL ES 2.0 APPLE-16.4.2
GL_VERSION (major.minor) = 0.0
CTXINFO vendor
CTXINFO renderer
CTXINFO glExtensions
CTXINFO GL_ARB_pixel_buffer_object
CTXINFO allocate the structure
CTXINFO set function pointers
Attributes = onScreen: trueredSize : 8, greenSize : 8, blueSize : 8, alphaSize : 8, depthSize : 24, doubleBuffer : true
  initialize() returns 140384843884416
IOSWindowSystemInterface : share 271e0 view 0 pf otready 
GL_VERSION string = OpenGL ES 2.0 APPLE-16.4.2
GL_VERSION (major.minor) = 0.0
CTXINFO vendor
CTXINFO renderer
CTXINFO glExtensions
CTXINFO GL_ARB_pixel_buffer_object
CTXINFO allocate the structure
CTXINFO set function pointers
Attributes = onScreen: trueredSize : 8, greenSize : 8, blueSize : 8, alphaSize : 8, depthSize : 24, doubleBuffer : true
  initialize() returns 140384847044992
Maximum supported texture size: 4096
Non power of two texture support = false
Maximum number of vertex attributes = 16
Maximum number of uniform vertex components = 512
Maximum number of uniform fragment components = 256
Maximum number of varying components = 32
Maximum number of texture units usable in a vertex shader = 8
Maximum number of texture units usable in a fragment shader = 8
Graphics Vendor: Apple Inc.
       Renderer: Apple Software Renderer
        Version: OpenGL ES 2.0 APPLE-16.4.2
 vsync: true vpipe: true
[WARN] java.lang.Class: Class.forName() failed to load 'com.sun.javafx.font.t2k.T2KFactory'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>com.sun.javafx.font.t2k.T2KFactory</pattern></forceLinkClasses> to your robovm.xml file to link it in.
2018-07-12 15:44:22.410 ContactApp[12971:469616] Done creating private storage /Users/sovandara/Library/Developer/CoreSimulator/Devices/6B6CA6F1-290F-4BC2-BAF3-47DEDD738A4A/data/Containers/Data/Application/A69488DB-53AA-4833-9BA8-E07EDDF44C46/Library/gluon
[WARN] java.lang.Class: Class.forName() failed to load 'javax.xml.stream.FactoryFinder$ClassLoaderFinderConcrete'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>javax.xml.stream.FactoryFinder$ClassLoaderFinderConcrete</pattern></forceLinkClasses> to your robovm.xml file to link it in.
Jul 12, 2018 3:44:22 PM java.util.logging.LoggingProxyImpl log
WARNING: Loading FXML document with JavaFX API of version 10.0.1 by JavaFX runtime of version 8.0.72-ea
path=/Users/sovandara/Library/Developer/CoreSimulator/Devices/6B6CA6F1-290F-4BC2-BAF3-47DEDD738A4A/data/Containers/Data/Application/A69488DB-53AA-4833-9BA8-E07EDDF44C46/Library/gluon
dbUrl=jdbc:h2:/Users/sovandara/Library/Developer/CoreSimulator/Devices/6B6CA6F1-290F-4BC2-BAF3-47DEDD738A4A/data/Containers/Data/Application/A69488DB-53AA-4833-9BA8-E07EDDF44C46/Library/gluon/CONTACT;FILE_LOCK=FS;PAGE_SIZE=1024;CACHE_SIZE=8192
getConnection=
[WARN] java.lang.Class: Class.forName() failed to load 'org.h2.upgrade.v1_1.Driver'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>org.h2.upgrade.v1_1.Driver</pattern></forceLinkClasses> to your robovm.xml file to link it in.
IOException: javafx.fxml.LoadException: 
file:/Users/sovandara/Library/Developer/CoreSimulator/Devices/6B6CA6F1-290F-4BC2-BAF3-47DEDD738A4A/data/Containers/Bundle/Application/AA0E7D96-ED31-4898-B6D8-05E7DD037568/ContactApp.app/lib/classes28.jar!/com/khmerdev/views/contact.fxml

[WARN] java.lang.Class: Class.forName() failed to load 'com.sun.javafx.tk.quantum.QuantumMessagesBundle_en_US'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>com.sun.javafx.tk.quantum.QuantumMessagesBundle_en_US</pattern></forceLinkClasses> to your robovm.xml file to link it in.
[WARN] java.lang.Class: Class.forName() failed to load 'com.sun.javafx.tk.quantum.QuantumMessagesBundle_en'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>com.sun.javafx.tk.quantum.QuantumMessagesBundle_en</pattern></forceLinkClasses> to your robovm.xml file to link it in.
[WARN] java.lang.Class: Class.forName() failed to load 'com.sun.javafx.tk.quantum.QuantumMessagesBundle'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>com.sun.javafx.tk.quantum.QuantumMessagesBundle</pattern></forceLinkClasses> to your robovm.xml file to link it in.
[WARN] java.lang.Class: Class.forName() failed to load 'com.oracle.jrockit.jfr.FlightRecorder'. Use the -forcelinkclasses command line option or add <forceLinkClasses><pattern>com.oracle.jrockit.jfr.FlightRecorder</pattern></forceLinkClasses> to your robovm.xml file to link it in.
setSwapInterval(1)
max rectangle texture cell size = 89
wrap rectangle texture = 2 x 2
ES2ResourceFactory: Prism - createStockShader: AlphaTexture_Color.frag
PPSRenderer: scenario.effect - createShader: LinearConvolveShadow_20
ES2ResourceFactory: Prism - createStockShader: Solid_TextureRGB.frag
Growing pool ES2 Vram Pool target to 73,400,324
Growing pool ES2 Vram Pool target to 88,080,388
ES2ResourceFactory: Prism - createStockShader: Texture_Color.frag
Growing pool ES2 Vram Pool target to 97,533,956
PPSRenderer: scenario.effect - createShader: LinearConvolveShadow_64
Growing pool ES2 Vram Pool target to 105,922,564
setSwapInterval(0)
setSwapInterval(1)
setSwapInterval(0)
setSwapInterval(1)
Growing pool ES2 Vram Pool target to 117,211,140
PPSRenderer: scenario.effect - createShader: LinearConvolve_16

Please check the following screenshot

Window

Tablet

Mac

iPad


Solution

  • When you run H2 on iOS, the problem is not related to forceLinkClasses (it is probably required anyway), but to a missing implementation of the javax.management package, as you will see in the logs:

    java.lang.NoClassDefFoundError: java/lang/management/ManagementFactory
    

    This means that the RoboVM implementation of the Java packages didn't add the java.lang.management/ javax.management classes.

    Since H2 is open source, you could remove the JMX references and use your custom build, or we could try to bypass the issue on iOS, providing a dummy implementation of the missing classes.

    I've tested only the GluonSQLite sample (but with H2 instead of SQLite), adding these two classes:

    java.lang.management.ManagementFactory

    package java.lang.management;
    
    import java.util.ArrayList;
    import java.util.List;
    import javax.management.MBeanServer;
    import javax.management.ThreadMXBean;
    import javax.management.GarbageCollectorMXBean;
    
    public class ManagementFactory {
    
        public static synchronized MBeanServer getPlatformMBeanServer() {
            return new MBeanServer();
        }
    
        public static ThreadMXBean getThreadMXBean() {
            return new ThreadMXBean();
        }
    
        public static List<GarbageCollectorMXBean> getGarbageCollectorMXBeans() {
            return new ArrayList<>();
        }
    
        public static OperatingSystemMXBean getOperatingSystemMXBean() {
            return new OperatingSystemMXBean();
        }
    }
    

    java.lang.managementOperatingSystemMXBean

    package java.lang.management;
    
    public class OperatingSystemMXBean {
    
        public String getName() {
            return "";
        }
    
        public String getArch() {
            return "";
        }
    
        public String getVersion() {
            return "";
        }
    
        public int getAvailableProcessors() {
            return 0;
        }
    
        public double getSystemLoadAverage() {
            return 0;
        }
    }
    

    and these three empty classes:

    package javax.management;
    public class GarbageCollectorMXBean { }
    
    package javax.management;
    public class MBeanServer { }
    
    package javax.management;
    public class ThreadMXBean { }
    

    And including: 'org.h2.**.*' and 'javax.management.**.*' to forceLinkClasses on the build.gradle file, the sample works on iOS successfully (H2 connection, database creation, insert, select, ...).