I have pretty simple desktop app written in JavaFX. The only task is to get a search term (text input box), send a request to my REST API and display results in a form of articles list (name and photo). When I try to convert this app to native Android app using Gluon, there is an error that I can't figure out how to fix.
The question is: How to fix this error and run my app on Android device?
Tools (software) I use are:
Ubuntu 22.04 LTS,
Java 17,
graalvm-svm-java17-linux-gluon-22.1.0.1-Final,
Maven 3.8.8,
android-ndk-r25c.
EDIT: Tried with Java 11 and there is no difference.
Steps I do:
$ mvn -Pandroid gluonfx:build
$ mvn -Pandroid gluonfx:package
$ mvn -Pandroid gluonfx:install
$ mvn -Pandroid gluonfx:nativerun
Last step is not working because I get an error:
[INFO] [SUB] --------- beginning of crash
[INFO] [SUB] E/AndroidRuntime(28624): FATAL EXCEPTION: main
[INFO] [SUB] E/AndroidRuntime(28624): Process: com.thevegcat.TheVegCatApp, PID: 28624
[INFO] [SUB] E/AndroidRuntime(28624): java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "JNI_OnLoad_javajpeg" referenced by "/data/app/~~MsvDADBiJ40PbJ6YgoWjVw==/com.thevegcat.TheVegCatApp-jeiDsmOewudHMN1dhopxxA==/lib/arm64/libsubstrate.so"...
[INFO] [SUB] E/AndroidRuntime(28624): at java.lang.Runtime.loadLibrary0(Runtime.java:1077)
[INFO] [SUB] E/AndroidRuntime(28624): at java.lang.Runtime.loadLibrary0(Runtime.java:998)
[INFO] [SUB] E/AndroidRuntime(28624): at java.lang.System.loadLibrary(System.java:1656)
[INFO] [SUB] E/AndroidRuntime(28624): at com.gluonhq.helloandroid.MainActivity.surfaceCreated(MainActivity.java:107)
Workaround that can create fake symbols (without real implementations): https://blogs.oracle.com/javamagazine/post/java-javafx-game-mobile-graalvm
After fixing already mentioned first symbol, new symbols show up and finally when I fix them all, app starts, but it shows only black blank screen. When I tap device's screen, I get response in debug log which tells me it's working, but not as expected because all those missing symbols were about graphics which are now not available (empty methods without implementations created by this dirty fix).
EDIT: Found that I'm not the only one: https://github.com/gluonhq/substrate/pull/1000
EDIT: Method to be referenced in comments below
private Image fetchImage(final String photo) {
final String imageUrl = this.host + photo;
try {
final BufferedImage image = ImageIO.read(new URL(imageUrl));
return SwingFXUtils.toFXImage(image, null);
}
catch (final IOException e) {
return null;
}
}
pom.xml
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<name>The Vegan Catalog App</name>
<description>The Best World Vegan Catalog by H.Lo</description>
<groupId>com.thevegcat</groupId>
<artifactId>TheVegCatApp</artifactId>
<version>1.0.2</version>
<packaging>jar</packaging>
<developers>
<developer>
<id>HLo</id>
<name>Hrvoje Lončar</name>
<email>[email protected]</email>
</developer>
</developers>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<main.class>com.thevegcat.app.TheVegCatApp</main.class>
<launcher.class>com.thevegcat.app.Launcher</launcher.class>
<javafx.LTS.version>17.0.7</javafx.LTS.version>
<httpclient5.version>5.2.1</httpclient5.version>
<jersey-client.version>3.1.2</jersey-client.version>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-shade-plugin.version>3.4.1</maven-shade-plugin.version>
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
<maven-assembly-plugin.version>3.6.0</maven-assembly-plugin.version>
<jakarta.ws.rs-api.version>3.1.0</jakarta.ws.rs-api.version>
<jakarta.activation-api.version>2.1.2</jakarta.activation-api.version>
<lombok.version>1.18.28</lombok.version>
<javafx-maven-plugin.version>0.0.8</javafx-maven-plugin.version>
<webp-imageio.version>0.1.6</webp-imageio.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<!-- BEGIN Android attempt -->
<charm-glisten.version>6.2.3</charm-glisten.version>
<connect.version>2.0.1</connect.version>
<attach.version>4.0.18</attach.version>
<gluonfx.plugin.version>1.0.18</gluonfx.plugin.version>
<!-- END Android attempt -->
</properties>
<dependencies>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>10.0.0</version>
</dependency>
<!-- BEGIN Android attempt -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
<version>${javafx.LTS.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.LTS.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
<version>1.1.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.gluonhq.attach</groupId>
<artifactId>display</artifactId>
<version>${attach.version}</version>
</dependency>
<dependency>
<groupId>com.gluonhq.attach</groupId>
<artifactId>lifecycle</artifactId>
<version>${attach.version}</version>
</dependency>
<dependency>
<groupId>com.gluonhq.attach</groupId>
<artifactId>statusbar</artifactId>
<version>${attach.version}</version>
</dependency>
<dependency>
<groupId>com.gluonhq.attach</groupId>
<artifactId>storage</artifactId>
<version>${attach.version}</version>
</dependency>
<dependency>
<groupId>com.gluonhq.attach</groupId>
<artifactId>device</artifactId>
<version>${attach.version}</version>
</dependency>
<dependency>
<groupId>com.gluonhq.attach</groupId>
<artifactId>push-notifications</artifactId>
<version>${attach.version}</version>
</dependency>
<dependency>
<groupId>com.gluonhq.attach</groupId>
<artifactId>runtime-args</artifactId>
<version>${attach.version}</version>
</dependency>
<dependency>
<groupId>com.gluonhq.attach</groupId>
<artifactId>util</artifactId>
<version>${attach.version}</version>
</dependency>
<!-- END Android attempt -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.LTS.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>${javafx.LTS.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.LTS.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.LTS.version}</version>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>${jakarta.ws.rs-api.version}</version>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>${jakarta.activation-api.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>${httpclient5.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey-client.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey-client.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
<version>${jersey-client.version}</version>
</dependency>
<dependency>
<groupId>org.sejda.imageio</groupId>
<artifactId>webp-imageio</artifactId>
<version>${webp-imageio.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<release>${java.version}</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>${javafx-maven-plugin.version}</version>
<configuration>
<mainClass>${main.class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>project-classifier</shadedClassifierName>
<outputFile>shade\TheVegCatApp.jar</outputFile>
<transformers>
<transformer implementation=
"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${launcher.class}</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>${main.class}</mainClass>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>${maven-assembly-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>${main.class}</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!-- BEGIN Android attempt -->
<plugin>
<groupId>com.gluonhq</groupId>
<artifactId>client-maven-plugin</artifactId>
<version>0.1.31</version>
<configuration>
<mainClass>${main.class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>com.gluonhq</groupId>
<artifactId>gluonfx-maven-plugin</artifactId>
<version>${gluonfx.plugin.version}</version>
<configuration>
<target>${gluonfx.target}</target>
<mainClass>${main.class}</mainClass>
<attachList>
<list>display</list>
<list>lifecycle</list>
<list>statusbar</list>
<list>storage</list>
<list>device</list>
<list>push-notifications</list>
<list>runtime-args</list>
</attachList>
<reflectionList>
<list>com.thevegcat.sample.User</list>
</reflectionList>
<!-- ugly fix for 'cannot locate symbol {} referenced by {}'
caused by bad linking that is made by gluon or graalvm -->
<!--
<linkerArgs>
<arg>android/missing_symbols.o</arg>
</linkerArgs>
-->
</configuration>
</plugin>
<!-- END Android attempt -->
</plugins>
</build>
<!-- BEGIN Android attempt -->
<profiles>
<profile>
<id>android</id>
<properties>
<gluonfx.target>android</gluonfx.target>
</properties>
</profile>
<profile>
<id>ios</id>
<properties>
<gluonfx.target>ios</gluonfx.target>
</properties>
</profile>
</profiles>
<!-- END Android attempt -->
</project>