© Stephen Chin, Johan Vos and James Weaver 2019
S. Chin et al.The Definitive Guide to Modern Java Clients with JavaFXhttps://doi.org/10.1007/978-1-4842-4926-0_11

11. Native Mobile Apps for iOS and Android

Stephen Chin1 , Johan Vos2 and James Weaver3
(1)
Belmont, CA, USA
(2)
Leuven, Belgium
(3)
Marion, IN, USA
 

Written by José Pereda and Johan Vos

Java started as a programming language for embedded devices. In the early nineties, a team inside Sun Microsystems worked on a software stack for a set of next generation hardware products.

The prototype of those hardware products, called the Star 7, looked like something between a mobile phone and a tablet. The software for these devices, originally code-named Oak, is what we currently know as Java.

Hardware and software evolutions took very different paths. The Java software made big furor for animating web pages, using Applets, and later became the preferred language for developing enterprise and cloud applications.

The hardware evolution was shaped by complex interactions between phone and device manufacturers, telecom operators, and content providers. The business models were very fragmented, and for a very long time, there was no easy access for developers in general to mobile devices.

With the growing popularity of app stores, it became easier for developers to write applications for mobile platforms. Also, the majority of mobile phones are now either Android based or iOS based. This reduces the fragmentation to two main platforms.

And with Java being originally developed for mobile and embedded systems, it very much makes sense to create Java apps and run them on mobile devices, especially since recent evolutions in the Java area make it easy to deliver high-performant apps to mobile devices.

Why JavaFX on Mobile

In today’s digital infrastructure, web pages are often used to render information from a backend in a simple way.

Desktop and laptop systems are widely used for working with applications that require user interaction, data synchronization, high-performance rendering, and cloud integration. As such, they complement web applications.

An evolution in the IT landscape leads to an increased importance of mobile devices, for example, phones and tablets. As a consequence, IT backends now have to serve three different channels: web-based frontends, desktop applications, and mobile apps. This is shown in Figure 11-1.
../images/468104_1_En_11_Chapter/468104_1_En_11_Fig1_HTML.jpg
Figure 11-1

Business backend serving different channels

JavaFX, thanks to it being Java and hence supporting the Write Once, Run Anywhere paradigm, is very well suited for creating applications that work on desktops and laptops, but also on phones and tablets.

In the previous chapter, we showed how you can convert your JavaFX applications into applications that are native to the client. When we talk about “native,” we mean a combination of two things:
  1. 1.

    Code execution is in the native approach of the device. The application that is delivered to the phone directly executes machine code and is not interpreted or translated on the device. This allows for fast execution.

     
  2. 2.

    There is no intermediate rendering engine (e.g., a browser), and the JavaFX controls are rendered directly using the hardware-accelerated graphics engine available on the client.

     

In this chapter, we will explain how to deploy your JavaFX applications on mobile devices, thereby leveraging the hardware-accelerated native rendering used on those devices.

Different Approaches for Mobile Apps

There are a number of approaches for creating applications for mobile devices, and they can be divided into a number of categories. Any categorization is artificial, so the one that we use here is only one possibility.

We consider three different approaches, as shown in Figure 11-2:
../images/468104_1_En_11_Chapter/468104_1_En_11_Fig2_HTML.jpg
Figure 11-2

Three possible approaches for mobile apps

  1. 1.

    Web-based mobile applications

     
  2. 2.

    Applications using hardware-accelerated native rendering

     
  3. 3.

    Applications using OS-specific native controls

     

Each of these options has its advantages, and there are valid use cases for the three options.

OS-Specific Native Controls

Using OS-specific native controls, as is the case in approach 3, allows for a real smooth integration with the native operating system. In this case, the native controls (e.g., buttons, labels, lists, etc.) are used to render the user interface. For the end user, this is convenient, as he recognizes the typical UI components he also uses in other apps.

This approach requires very specific skills related to the target operating system. Since most mobile apps target both Android and iOS, and since those platforms come with their own approach for native user interfaces, the apps following this approach are typically created by different teams: one for Android and one for iOS.

Moreover, the OS-specific native controls are subject to fast changes. While many end users love the fast innovation in those environments, for software developers, it is often problematic as they have to upgrade their native apps very often or risk that they are outdated.

Mobile Web Sites

The simplest approach is often simply creating a mobile-friendly web site and have that rendered in the mobile browser that is available on mobile devices – optionally integrated with an app icon so that users can start the application more easily.

In theory, the same web site that is used for rendering in desktop browser can be rendered on mobile. However, those web sites are typically created for large screens and are operated using mouse controls. Clicking a button using a mouse is easier than touching the same button on a touch device. In general, a web experience is much different from a mobile experience. Users who are used to work with native apps on mobile devices often get frustrated with web sites, and this damages the brand.

Device Native Rendering

The JavaFX approach falls into the second category. JavaFX has its own set of controls, and developers can easily create their own controls. The rendering of JavaFX is done on top of the hardware-accelerated drivers on the target platform. At this moment, the rendering pipelines for both JavaFX for iOS and JavaFX for Android use OpenGL, using the same code as the rendering on Mac OS X and Linux. OpenGL is a very mature and stable protocol, and changes in the native UI controls of iOS or Android do not impact OpenGL development. In fact, native OpenGL is also used by a number of game developers, who want to achieve maximum performance and flexibility on mobile devices.

Compared to rendering using a web browser, the JavaFX rendering is much more “native” as it doesn’t need the intermediate web browser, but directly targets the same native drivers that are also used for rendering native iOS or native Android controls.

At its core, both iOS and most Android devices are systems with an AArch64 processor, running some sort of Linux. Those systems, as well as OpenGL, are widely used in the industry and not controlled by a single mobile device manufacturer. As a consequence, they provide a stable base, avoiding a vendor lock.

The application-specific JavaFX stack is shown in Figure 11-3.
../images/468104_1_En_11_Chapter/468104_1_En_11_Fig3_HTML.jpg
Figure 11-3

JavaFX stack

Hello, JavaFX on iOS

Note: At the time of writing, it is possible to create applications for JavaFX 11 and beyond and deploy them to iOS devices. Work for deploying them on Android devices is underway and is expected to be ready before publication date. While Android and iOS are very different systems, the experience for Java developers will be very similar. Hence, although we only discuss iOS here, the same principles and tools will work for Android as well.

Client Plugins to Reduce Complexity

Deploying applications to mobile devices is slightly more complex than deploying applications on your local development machine (a laptop or a desktop). There are mainly two reasons for this:
  • When compiling an application that has to be executed on the same system as the one we use to compile, we can leverage the existing toolchain of our development system, including the compiler and linker. However, when compiling an application for another system, we need to take into account the operating system and the architecture of that system. This is often called cross-compiling, which is considered more complex.

  • The operators of the mobile app stores, Google and Apple, each have their own additional requirements for creating mobile apps, for example, related to signing and the structure of the final executable bundles.

One of the reasons for the general success of Java is the large ecosystem. Different companies and individuals provide a set of tools and libraries that make it easier for fellow developers to work on their project.

For JavaFX on mobile, Gluon created a number of plugins that make it much easier for Java developers to deploy Java apps on mobile devices. Those plugins deal with the complexity of cross-compilation and the specific requirements from the respective app stores.

The plugins are currently available for Maven and Gradle projects. Since the popular Java IDEs have first-class support for Maven and Gradle, it is easy to use existing IDEs to create and maintain mobile JavaFX apps.

Using Maven or Gradle build tools in our project, we can use the JavaFX plugin to run the project on your local (development) system and the Gluon Client plugin to create a native image that can be deployed to a target platform.

The Development Flow

While it is, at least in theory, possible to create a mobile app and only test/run it on a mobile device, it is highly recommended to work on desktop first.

A typical deployment cycle contains a number of steps:
  • Write some code.

  • Compile the code.

  • Run the code.

  • Test if the output and behavior is as expected.

These steps often have to be repeated, resulting in a number of deployment cycles for a given project.

It should be clear that the deployment cycle on mobile devices takes more time than the deployment cycle on desktop devices. Especially compiling the code takes more time for mobile devices. Therefore, it is better to use your desktop or laptop development system for deployment. The tools that we will describe in the following enable you to use the exact same code on mobile and desktop devices, and the behavior is similar too.

Of course, the mobile experience is still different, and that can only be really tested on mobile devices. For example, gestures like rotate, pinch, and zoom have to be fine-tuned on specific mobile devices, in order to be as intuitive as possible.

A typical development flow for mobile apps is shown in Figure 11-4.
../images/468104_1_En_11_Chapter/468104_1_En_11_Fig4_HTML.jpg
Figure 11-4

Development flow of mobile apps

In the referred figure, most of the development is done on desktop. The application is improved, both the business logic part and the UI part. In this stage, issues like NullPointerExceptions, wrong values, or incorrect UI elements are fixed.

At a given moment, the application works as expected on desktop. The business logic fits the requirements, and the UI follows the design. At this point, the application is deployed on a mobile device and tested. As a result of these tests, a new cycle is performed on desktop or on mobile. For example, if tests on the mobile device lead to a hidden issue to surface, it is recommended to go back to the desktop cycle and add a test that fails. This issue can be worked on at the desktop.

On the other hand, if an issue is detected that is specific to mobile (e.g., zooming goes too fast), this issue can directly be fixed in a development cycle on the mobile device.

The Code

We will run you through a very simple HelloFX application that is executed on an iPhone or an iPad. This sample can be found here (Gradle and Maven):

https://github.com/modernclientjava/mcj-samples/tree/master/ch11-Mobile/Gradle/HelloFX

https://github.com/modernclientjava/mcj-samples/tree/master/ch11-Mobile/Maven/HelloFX

Listing 11-1 shows the HelloFX main class, and Listing 11-2 shows the styles.css file.
package hellofx;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class HelloFX extends Application {
    public void start(Stage stage) {
        String javaVersion = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");
        Label label = new Label("Hello, JavaFX " + javafxVersion +
           ", running on Java " + javaVersion + ".");
        ImageView imageView = new ImageView(
           new Image(HelloFX.class.getResourceAsStream("openduke.png")));
        imageView.setFitHeight(200);
        imageView.setPreserveRatio(true);
        VBox root = new VBox(30, imageView, label);
        root.setAlignment(Pos.CENTER);
        Scene scene = new Scene(root, 640, 480);
        scene.getStylesheets().add(
           HelloFX.class.getResource("styles.css").toExternalForm());
        stage.setScene(scene);
        stage.show();
    }
   public static void main(String[] args) {
        launch(args);
    }
}
Listing 11-1

HelloFX.java file

.label {
    -fx-text-fill: blue;
}
Listing 11-2

File styles.css

Listing 11-3 shows the build.gradle file, and Listing 11-4 shows the settings.gradle file for a Gradle project.
plugins {
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
    id 'com.gluonhq.client-gradle-plugin' version '0.0.12'
}
repositories {
    mavenCentral()
}
gluonClient {
    // target = "ios”
}
javafx {
    version = "12.0.2"
    modules = [ "javafx.controls" ]
}
mainClassName = 'hellofx.HelloFX'
Listing 11-3

File build.gradle

pluginManagement {
    repositories {
        maven {
            url "https://nexus.gluonhq.com/nexus/content/repositories/releases"
        }
        gradlePluginPortal()
    }
}
rootProject.name = 'HelloFX'
Listing 11-4

File settings.gradle

The build.gradle file shows that we use two special plugins, apart from the regular application one:
    id 'org.openjfx.javafxplugin' version '0.0.8'
    id 'com.gluonhq.client-gradle-plugin' version '0.0.12'

javafxplugin is the general plugin for developing JavaFX applications and dealing with the JavaFX modules and dependencies. This is the plugin you typically use for developing JavaFX applications.

The client-gradle-plugin is Gluon’s plugin that is capable of cross-compiling code for iOS and Android devices.

In order to use those plugins, we have to tell Gradle where to search for plugins. The javafxplugin is in the general Gradle plugin portal, while the client-gradle-plugin is hosted in a Gluon repository. This explains the settings.gradle file shown in Listing 11-4.

Similar to the build file for Gradle, you can use a pom file to declare how to build the application using Maven.

Listing 11-5 shows the equivalent pom file for a Maven project.
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>hello</groupId>
    <artifactId>hellofx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>hellofx</name>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <javafx.version>12.0.2</javafx.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javafx.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.3</version>
                <configuration>
                    <mainClass>hellofx.HelloFX</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.gluonhq</groupId>
                <artifactId>client-maven-plugin</artifactId>
                <version>0.0.12</version>
                <configuration>
                    <!-- <target>ios</target>-->
                    <mainClass>hellofx.HelloFX</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <pluginRepositories>
        <pluginRepository>
            <id>gluon-releases</id>
            <url>http://nexus.gluonhq.com/nexus/content/repositories/releases/</url>
        </pluginRepository>
    </pluginRepositories>
</project>
Listing 11-5

File pom.xml

Requirements

To use the plugin to develop and deploy native applications on iOS platforms, you need a Mac with Mac OS X 10.13.2 or superior and Xcode 9.2 or superior, available from the Mac App Store. Once Xcode is downloaded and installed, open it to accept the license terms.

You need a JDK 11 distribution for Mac OS X that doesn’t bundle JavaFX, like the following:
Once downloaded and installed, you need to set JAVA_HOME pointing to that JDK, like
export JAVA_HOME=/Users/<user>/Downloads/jdk-11.0.2.jdk/Contents/Home

To build for iOS devices, you will also need LLVM installed and added to your path. You can download LLVM 6.0.0 for Mac OS X from

http://releases.llvm.org/6.0.0/clang+llvm-6.0.0-x86_64-apple-darwin.tar.xz

Once downloaded, add it to your path, editing ~/.bash_profile and adding
export PATH="$PATH:/Users/<user>/Downloads/clang+llvm-6.0.0-x86_64-apple-darwin/bin"

Finally, if you want to test and distribute your app via the App Store, you’ll need to enroll in the Apple Developer Program. However, if you only want to test on your own iOS device, you can use free provisioning.

Follow the instructions in this link to get a valid provisioning profile and a valid signing identity: https://docs.gluonhq.com/client/#_ios_deployment.

Build the Project

The first step is to build and run the project as a regular Java project (on a regular JVM that you use for your local development, e.g., hotspot).

With Gradle:
    ./gradlew clean build run
With Maven:
    mvn clean javafx:run
The result is shown in Figure 11-5.
../images/468104_1_En_11_Chapter/468104_1_En_11_Fig5_HTML.jpg
Figure 11-5

Running HelloFX on OpenJDK 11.0.2

These instructions will compile the application and its dependencies and run the resulting classes using the default Java Virtual Machine of your system. This is very similar to any other regular Java development. There is no cross-compilation involved here.

As we said before, it is important to verify the app works without errors on your development system, with a regular VM. The AOT compilation that will be performed when we deploy to mobile devices takes a long time, so it should be called only when the project is ready to go to this stage.

Once the project is ready, let’s configure the plugin to run for iOS:

With Gradle, edit the build.gradle file and set
    gluonClient {
         target = "ios"
    }

This line instructs the plugin to generate code for iOS devices and to package the application so that it fulfils the requirements for iOS applications.

If you prefer to test your application on the iPhone simulator, you can do that as well by specifying the target to be “ios-sim”, for example,
    gluonClient {
         target = "ios-sim"
    }
With Maven, edit the pom.xml file and set
    <configuration>
        <target>ios</target>
        <mainClass>hellofx.HelloFX</mainClass>
    </configuration>
Similarly, if you want to target the iPhone simulator, you have to specify “ios-sim” as target, for example,
    <configuration>
        <target>ios-sim</target>
        <mainClass>hellofx.HelloFX</mainClass>
    </configuration>

Make sure to save the changes to the file you modified.

We will now compile, package, and run the application on mobile.

Compile

The typical Gradle “run” task will check if the application is compiled; otherwise, it will compile the required classes again.

For running apps on mobile devices, the compile task has to be called manually, as it can take a long time. This is to allow developers to modify things that are not related to the Java files (but, e.g., to application icons) without having to go through the long compilation phase each time.

Compilation can be done both with Gradle and Maven.

Run with Gradle:
    ./gradlew nativeCompile
Run with Maven:
    mvn client:compile
Then wait for a while (depending on your machine, it could easily be 10 minutes or more) until the task finishes successfully. If you check the terminal, you could see the feedback provided during the process, like in Listing 11-6.
> Task :nativeCompile
Omega :: host triplet = x86_64-macos-macos
Omega :: target triplet = arm64-macos-ios
...
[SUB] [HelloFX:4407]        (cap):   1,579.93 ms
[SUB] [HelloFX:4407]        setup:   3,208.00 ms
[SUB] [HelloFX:4407]   (typeflow):  64,047.69 ms
[SUB] [HelloFX:4407]    (objects):  51,110.04 ms
[SUB] [HelloFX:4407]   (features):   5,944.12 ms
[SUB] [HelloFX:4407]     analysis: 124,486.39 ms
[SUB] [HelloFX:4407]     (clinit):   1,714.05 ms
[SUB] [HelloFX:4407]     universe:   6,711.85 ms
[SUB] [HelloFX:4407]      (parse):   8,319.86 ms
[SUB] [HelloFX:4407]    (compile):  88,166.16 ms
[SUB] [HelloFX:4407]    (bitcode):   7,262.72 ms
[SUB] [HelloFX:4407]       (link):  53,229.43 ms
[SUB] [HelloFX:4407]         (gc):  40,510.46 ms
[SUB] [HelloFX:4407]       (llvm): 334,081.88 ms
[SUB] [HelloFX:4407]   (stackmap):  20,373.89 ms
[SUB] [HelloFX:4407]      compile: 555,012.93 ms
[SUB] [HelloFX:4407]        image:  17,508.66 ms
[SUB] [HelloFX:4407]        write:  32,804.77 ms
[SUB] [HelloFX:4407]      [total]: 743,535.62 ms
BUILD SUCCESSFUL in 12m 32s
Listing 11-6

Output during the native compilation phase

As a result, HelloFX.o, with 74.7 MB can be found under build/client/ios-arm64/gvm/tmp/SVM-15***/HelloFX.o.

If that is not the case, check for any possible failure in the log files under build/client/ios-arm64/gvm/log.

The next steps will go faster, but they will require a valid signing identity and a valid provisioning profile in order for you to sign the app before it can be deployed to the device.

Link

Now that the Java code for the application is compiled to native code, we can package the generated code with the required libraries and resources, sign the application, and perform more iOS-specific tasks.

The plugins combine this packaging in the nativeLink task .

Run with Gradle:
    ./gradlew nativeLink
Run with Maven:
    mvn client:link

It produces build/client/ios-arm64/hellofx.app (136.6 MB).

Run

Congratulations! Your mobile app is ready now! You can deploy this app to your phone as explained in the following.

Plug in your iOS device, and run with Gradle:
    ./gradlew nativeRun
Or run with Maven:
    mvn client:run
Note that you will need to unlock your device. One installed, it will launch (Figure 11-6).
../images/468104_1_En_11_Chapter/468104_1_En_11_Fig6_HTML.jpg
Figure 11-6

HelloFX app on iOS

How Does It Work?

As a Java developer in general, and a JavaFX developer in particular, you can focus on the Java APIs and implement the required code that make your application work. The Java platform itself will make sure that your Java application is translated to Java bytecode, which is not dependent on the operating system (e.g., Windows, Mac OS, Linux variants, iOS) or processors (e.g., ARM 64, Intel X86-64, ARMv6hf).

The typical Java approach is that the next step, executing the Java bytecode on a native system, is achieved using a Java Virtual Machine (JVM) running on the target system. In this case, the JVM interprets the bytecode and executes it, using target-specific native instructions. Most JVMs also contain a Just In Time compiler (JIT) that converts frequently used Java methods on the fly into native code. In general, native code runs much faster than interpreted code; hence, the performance of a Java application typically improves after it is running for a while. Compiling the java bytecode to native code takes some time, and in the meantime the application is running, so peak performance is not immediately achieved.

In the previous chapter, we introduced the Graal Native Image tool for creating native packages based on JavaFX applications. The advantages discussed in the previous chapter apply for the mobile case as well, and there is an additional reason for using this approach on mobile. Apple does not allow dynamic code to be generated at runtime on iOS devices. Hence, the typical Java approach, where code is first interpreted and later (while running) optimized, is not allowed on iOS devices.

Running mobile Java applications using an interpreter only at runtime is possible, but the interpreter is much slower than executing native code.

On top of that, the (old) server-side approach where one JVM installation can handle a large number of applications is not very common on mobile. Mobile apps are self-contained applications that bundle all their dependencies – apart from a small set of APIs that are offered by the native operating system. It is absolutely not done to download additional libraries or components at runtime in order to satisfy dependencies needed by a specific application.

The Gluon Client plugin, demonstrated in the previous section, contains the required tools to convert Java applications to bytecode; invokes the Graal Native Image tool to convert the bytecode into native code, including the required VM functionality; and links the result into an executable that can be deployed to mobile devices.

Schematically, this is shown in Figure 11-7.
../images/468104_1_En_11_Chapter/468104_1_En_11_Fig7_HTML.jpg
Figure 11-7

Native application workflow

The resulting native image obtained by the Gluon Client tools is conceptually not different from a native image created using the OS-specific tools (e.g., Xcode for creating iOS apps and Android Studio for creating Android apps). The developer still needs to upload this image to the app stores, thereby documenting the application, providing screenshots, and so on.

Using the Plugin Options

By default, the plugin will use the best configuration to deploy your application to mobile devices. Different components in the plugin and in GraalVM analyze your code and its dependencies to decide on things like including native libraries, using reflection and JNI, and so on. It is expected that those analysis tools will improve over time.

Since it is currently not possible to cover all edge cases with the analysis tools, the plugin allows developers to set configuration-specific settings (e.g., additional classes to be used for reflection, including native symbols, etc.)

bundlesList

A list of additional full qualified bundle resources that will be added to the default bundles list that already includes
  • com/sun/javafx/scene/control/skin/resources/controls

  • com.sun.javafx.tk.quantum.QuantumMessagesBundle

For instance, if you are using a resource bundle for internalization purposes, like src/resources/hellofx/hello.properties (and hello_EN.properties and others), you will need to include, using Gradle,
bundlesList = ["hellofx.hello"]
or using Maven:
<bundlesList>
    <list>hellofx.hello</list>
</bundlesList>

resourcesList

A list of additional resource patterns or extensions that will be added to the default resources list that already includes
  • png, gif, jpg, jpeg, bmp,

  • ttf, css, fxml, json,

  • frag, gls, license

For instance, if you are using a properties file (not included as a resource bundle), like src/resources/hellofx/logging.properties, you will need to include, using Gradle,
resourcesList = ["properties"]
or using Maven:
<resourcesList>
    <list>properties</list>
</resourcesList>

reflectionList

A list of additional full qualified classes that will be added to the default reflection list that already includes most of the JavaFX classes.

The current list is added to file under build/client/gvm/reflectionconfig-$target.json.

If you are using FXML in your project, since the FXMLLoader uses reflection to inspect the FXML file, you will need to add a few classes from this file to the reflection list: mainly the FXMLLoader itself (“javafx.fxml.FXMLLoader”), the controller class, and the controls classes (the containers are already included in this list).

jniList

A list of additional full qualified classes that will be added to the default jni list that already includes most of the JavaFX classes.

The current list is added to file under build/client/gvm/jniconfig-$target.json.

delayInitList

A list of additional full qualified classes that will be added to the default delayed initialization list.

releaseSymbolsList

A list of additional JNI functions that will be added to the default release symbols list that already includes most of the JNI methods.

The current list is added to file under build/client/gvm/release.symbols.

Creating Real Mobile-Looking Apps

While a JavaFX application created for desktop works on mobile devices, in many cases, you can achieve an improved user experience by tuning your application for mobile usage. Doing so, you end up with code that is specific to mobile vs. specific to desktop, and you may ask what is then the advantage compared to doing mobile development in native languages. There are some very good reasons for this though:
  • Most of your application code can still be shared. All business logic, but also a large part of the UI code, can be shared between mobile and desktop.

  • With the appropriate frameworks (e.g., Glisten), many existing JavaFX controls get styled for mobile usage. In that case, the exact same JavaFX code is used.

  • For the UI components that are completely different between mobile and desktop, at the very least the code is all written in Java and can be written by the same developers, compiled by the same tools, and integrated in the same CI infrastructure.

A good architecture for client applications makes a clean separation between business logic and UI components. In JavaFX, the UI part of an application can be separated even more. The data is often maintained in instances of ObservableObject or ObservableList , which do not directly relate to the rendering details of the UI components – hence, they can be considered part of the general components.

There are a number of approaches to make the UI components of your JavaFX application more mobile specific, and we will briefly discuss the following:
  • Use different stylesheets for mobile vs. desktop.

  • Use specific controls for mobile.

The Glisten framework [see https://docs.gluonhq.com/charm/5.0.2/#_charm_glisten] from Gluon, which is part of Gluon Mobile, combines both approaches.

Different Stylesheets

One of the reasons why JavaFX user interfaces are flexible and easy to change is because of the support for CSS, which is discussed in Chapter 5, “Mastering Visual and CSS Design.” Stylesheets define the look and feel of different components of the user interface in a declarative way, using a CSS file. They are decoupled from the implementation logic, and this allows for a number of combinations.

Looking at the code for the HelloFX app we discussed earlier in this chapter, adding a stylesheet in a JavaFX application is easily done, for example, in the start method of the application:
        Scene scene = new Scene(root, 640, 480);
        scene.getStylesheets().add(
            HelloFX.class.getResource("styles.css").toExternalForm());
In this code snippet, we add the stylesheet called styles.css to the scene graph. The stylesheet we used here is very simple, and it describes the style of the text in a Label control:
.label {
    -fx-text-fill: blue;
}

We can create a second stylesheet, where we set the -fx-text-fill property to a different color, for example, red.

We can copy the styles.css file to styles2.css and edit it as follows:
.label {
    -fx-text-fill: red;
}
We modify our application now so that we load this stylesheet instead of the original one:
        Scene scene = new Scene(root, 640, 480);
        scene.getStylesheets().add(
            HelloFX.class.getResource("styles2.css").toExternalForm());

If we now run the application, we will see that the text color has changed indeed, as shown in Figure 11-8.

This shows how easy the user interface of an application can be reconfigured by supplying a different stylesheet, but we now changed the stylesheet for all deployments. What we really want is to load a different stylesheet based on the target platform.

An easy solution to this is to check for the system property “os.name” and based on that load a different stylesheet, as shown in this snippet:
        Scene scene = new Scene(root, 640, 480);
        If (System.getProperty("os.name").equals("ios")) {
            scene.getStylesheets().add(
                HelloFX.class.getResource("styles.css").toExternalForm());
        } else {
            scene.getStylesheets().add(
                HelloFX.class.getResource("styles2.css").toExternalForm());
        }
This snippet will cause the "styles.css" stylesheet to be applied in case we are running on iOS systems (both device or simulator), and it will use the "styles2.css" stylesheet in all other cases.
../images/468104_1_En_11_Chapter/468104_1_En_11_Fig8_HTML.jpg
Figure 11-8

HelloFX application with CSS

With Gluon Mobile, you can go further. One of the components that are bundled with Gluon Mobile is the open source framework Gluon Attach, which is developed at https://github.com/gluonhq/attach.

Gluon Attach contains a number of services that expose a Java API and that are implemented on the different platforms using specific APIs. Example services implemented by Gluon Attach are position, storage, in-app-billing, pictures, Bluetooth Low Energy, and so on.

Using Attach, it is possible to detect the platform (e.g., iOS or Android) and the dimensions (e.g., phone or tablet). This allows to load a specific stylesheet for, for example, iPad systems and another specific stylesheet for Android phones.

The following code snippet shows how you can detect this, and it will use a different stylesheet in each case:
        Scene scene = new Scene(root, 640, 480);
        if (Platform.isIOS()) {
            scene.getStylesheets().add(
                HelloFX.class.getResource("styles.css").toExternalForm());
        } else if (Platform.isAndroid()) {
            scene.getStylesheets().add(
                HelloFX.class.getResource("styles2.css").toExternalForm());
        }

In this case, we make use of com.gluonhq.attach.util.Platform, to get the current platform where the application is running on.

In order to be able to import this class, we need dependencies in the build.gradle file, shown in Listing 11-7.
repositories {
    mavenCentral()
    maven {
        url 'https://nexus.gluonhq.com/nexus/content/repositories/releases'
    }
}
dependencies {
    implementation "com.gluonhq:charm-glisten:6.0.1"
    implementation "com.gluonhq.attach:util:4.0.2"
}
Listing 11-7

Adding Attach to a Gradle project

We need similar dependencies in the pom file, shown in Listing 11-8.
<dependencies>
    <dependency>
        <groupId>com.gluonhq</groupId>
        <artifactId>charm-glisten</artifactId>
        <version>6.0.1</version>
    </dependency>
    <dependency>
        <groupId>com.gluonhq.attach</groupId>
        <artifactId>util</artifactId>
        <version>4.0.2</version>
    </dependency>
</dependencies>
 <repositories>
    <repository>
        <id>Gluon</id>
        <url>
          https://nexus.gluonhq.com/nexus/content/repositories/releases
        </url>
    </repository>
</repositories>
Listing 11-8

Adding Attach to a Maven project

Mobile-Specific Controls

While there is a lot we can do with stylesheet, some controls are really only relevant on mobile devices.

Creating a control for a mobile device is not different from creating a control for a desktop application, and it has already been discussed in Chapter 7, “Bridging Swing and JavaFX.”

Gluon Mobile contains a number of mobile-specific controls that are often encountered in typical mobile applications. A list of those controls is available at

https://docs.gluonhq.com/charm/javadoc/5.0.2/com/gluonhq/charm/glisten/control/package-summary.html.

As an example, we will show an application that uses a FloatingActionButton control. The project can be found here (Gradle and Maven):

https://github.com/modernclientjava/mcj-samples/tree/master/ch11-Mobile/Gradle/HelloGluon

https://github.com/modernclientjava/mcj-samples/tree/master/ch11-Mobile/Maven/HelloGluon

The Code

Listing 11-9 shows the build.gradle file , and Listing 11-10 shows the settings.gradle file for a Gradle project.
plugins {
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
    id 'com.gluonhq.client-gradle-plugin' version '0.0.12'
}
repositories {
    mavenCentral()
    maven {
        url 'https://nexus.gluonhq.com/nexus/content/repositories/releases'
    }
}
def platform = "desktop"
dependencies {
    implementation "com.gluonhq:charm-glisten:6.0.1"
}
gluonClient {
    target = platform == "desktop" ? "host" : "ios"
    attachConfig {
        services 'display', 'lifecycle', 'statusbar', 'storage'
    }
    reflectionList =
         ["com.gluonhq.impl.charm.glisten.control.skin.GlistenButtonSkin"]
}
javafx {
    version = "12.0.2"
    modules = [ "javafx.controls" ]
}
mainClassName = 'hellofx.HelloGluon'
Listing 11-9

File build.gradle

pluginManagement {
    repositories {
        maven {
            url "https://nexus.gluonhq.com/nexus/content/repositories/releases"
        }
        gradlePluginPortal()
    }
}
rootProject.name = 'HelloGluon'
Listing 11-10

File settings.gradle

If you have a Maven project, Listing 11-11 shows the equivalent pom file.
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>hello</groupId>
    <artifactId>hellogluon</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>hellogluon</name>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <javafx.version>12.0.2</javafx.version>
        <attach.version>4.0.2</attach.version>
        <mainClassName>hellofx.HelloGluon</mainClassName>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javafx.version}</version>
        </dependency>
        <dependency>
            <groupId>com.gluonhq</groupId>
            <artifactId>charm-glisten</artifactId>
            <version>6.0.1</version>
        </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>util</artifactId>
            <version>${attach.version}</version>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>Gluon</id>
            <url>
                https://nexus.gluonhq.com/nexus/content/repositories/releases
            </url>
        </repository>
    </repositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.3</version>
                <configuration>
                    <mainClass>${mainClassName}</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.gluonhq</groupId>
                <artifactId>client-maven-plugin</artifactId>
                <version>0.0.12</version>
                <configuration>
                    <!-- Uncomment to run on iOS: -->
                    <!-- <target>ios</target>-->
                    <attachList>
                        <list>display</list>
                        <list>lifecycle</list>
                        <list>statusbar</list>
                        <list>storage</list>
                    </attachList>
                    <reflectionList>
                        <list>
          com.gluonhq.impl.charm.glisten.control.skin.GlistenButtonSkin
                        </list>
                    </reflectionList>
                    <mainClass>${mainClassName}</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <pluginRepositories>
        <pluginRepository>
            <id>gluon-releases</id>
            <url>
                 https://nexus.gluonhq.com/nexus/content/repositories/releases
            </url>
        </pluginRepository>
    </pluginRepositories>
</project>
Listing 11-11

Pom.xml file

Listing 11-12 shows the HelloGluon main class, and Listing 11-13 shows the styles.css file.
package hellofx;
import com.gluonhq.attach.display.DisplayService;
import com.gluonhq.attach.util.Platform;
import com.gluonhq.charm.glisten.application.MobileApplication;
import com.gluonhq.charm.glisten.control.AppBar;
import com.gluonhq.charm.glisten.control.FloatingActionButton;
import com.gluonhq.charm.glisten.mvc.View;
import com.gluonhq.charm.glisten.visual.MaterialDesignIcon;
import com.gluonhq.charm.glisten.visual.Swatch;
import javafx.geometry.Dimension2D;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
public class HelloGluon extends MobileApplication {
    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> {
            FloatingActionButton fab =
                new FloatingActionButton(MaterialDesignIcon.SEARCH.text,
                    e -> System.out.println("Search"));
            ImageView imageView = new ImageView(new Image(
                HelloGluon.class.getResourceAsStream("openduke.png")));
            imageView.setFitHeight(200);
            imageView.setPreserveRatio(true);
            Label label = new Label("Hello, Gluon Mobile!");
            VBox root = new VBox(20, imageView, label);
            root.setAlignment(Pos.CENTER);
            View view = new View(root) {
                @Override
                protected void updateAppBar(AppBar appBar) {
                    appBar.setTitleText("Gluon Mobile");
                }
            };
            fab.showOn(view);
            return view;
        });
    }
    @Override
    public void postInit(Scene scene) {
        Swatch.LIGHT_GREEN.assignTo(scene);
        scene.getStylesheets().add(
            HelloGluon.class.getResource("styles.css").toExternalForm());
        if (Platform.isDesktop()) {
            Dimension2D dimension2D = DisplayService.create()
                    .map(display -> display.getDefaultDimensions())
                    .orElse(new Dimension2D(640, 480));
            scene.getWindow().setWidth(dimension2D.getWidth());
            scene.getWindow().setHeight(dimension2D.getHeight());
        }
    }
    public static void main(String[] args) {
        launch();
    }
}
Listing 11-12

HelloFX.java file

.label {
    -fx-font-size: 2em;
    -fx-text-fill: -primary-swatch-700;
}
Listing 11-13

File styles.css

Build the Project

The first step is to build and run the project as a regular Java project (on a regular JVM that you use for your local development, e.g., hotspot).

With Gradle:
    ./gradlew clean build run
With Maven:
    mvn clean javafx:run
The result is shown in Figure 11-9.
../images/468104_1_En_11_Chapter/468104_1_En_11_Fig9_HTML.jpg
Figure 11-9

Running HelloGluon on OpenJDK 11.0.2

Once the project is ready, let’s configure the plugin to run for iOS.

With Gradle, edit the build.gradle file and set
    gluonClient {
         target = "ios"
         ...
    }
With Maven, edit the pom.xml file and set
    <configuration>
        <target>ios</target>
        ...
    </configuration>

Make sure to save the changes to the file you modified.

We will now compile, package, and run the application on mobile.

Compile

Run with Gradle:
    ./gradlew nativeCompile
Or run with Maven:
    mvn client:compile

Link

Run with Gradle:
    ./gradlew nativeLink
Or run with Maven:
    mvn client:link

Run

Plug in your iOS device, and run with Gradle:
    ./gradlew nativeRun
Or run with Maven:
    mvn client:run
Note that you will need to unlock your device. Once installed, it will launch (Figure 11-10).
../images/468104_1_En_11_Chapter/468104_1_En_11_Fig10_HTML.jpg
Figure 11-10

HelloGluon app on iOS

Summary

JavaFX applications are very well suited for being deployed on mobile devices. The combination of the JavaFX platform and the Graal Native Image component, integrated in the Gluon Mobile client packages, makes it possible for all Java developers to use their Java skills and create apps that can be uploaded to the popular mobile app stores.

There are a number of tools available that help developers to deploy Java and JavaFX apps to mobile devices in a very familiar way.

In order to make applications really mobile-friendly and adapt to the mobile device context, a number of frameworks can be used (e.g., Gluon Attach and Glisten).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.52.88