1. Introduction

Gluon provides an easy and modern approach for developing Java Client applications. These applications can run on the JVM or can be converted to a platform specific native-images which have lighting fast startup and takes a fraction of space. Moreover, applications can also be targeted to Android, iOS, and embedded apart from all the desktop environments.

Gluon provides all the tools necessary to build these applications including, but not limited to, build tools, IDE plugins, UI library, cloud connectivity, data-binding etc.

2. Getting Started

Java application as native application

Any Java application can be converted to a native application for a specific platform using Gluon technology. This platform specific application has much faster startup time, since the JVM no longer needs to be started. The resulting application will be entirely integrated into the native operating system.

An easy way to get started is just by adding the GluonFX plugin to your Java application. GluonFX plugin leverages GraalVM and OpenJDK by compiling the Java client application and all its required dependencies into native code, so that it can be directly executed as a native application on the target platform.

At this moment, Gluon has support for the following target platforms and architectures:

  • Linux (x86_64 and AArch64)

  • Mac OS X (x86_64 and AArch64)

  • Windows (x86_64)

  • iOS (x86_64 and arm64)

  • Android (AArch64)

Java library as native shared library

In a similar way, Java libraries can be converted to native shared libraries, providing these are pure Java projects (no UI/JavaFX involved), that can be used by third party projects. Currently, Gluon has support for the following target platforms and architectures:

  • Linux (x86_64 and AArch64)

  • Mac OS X (x86_64 and AArch64)

  • Windows (x86_64)

  • iOS (x86_64 and arm64)

  • Android (AArch64)

2.1. Requirements

Gluon applications can be developed, run, and tested on any desktop and embedded platform using JDK 11 or greater.

The platform specific requirements for the creation and deployment of native images are discussed in depth in the platforms section.

2.2. Gluon Start

Gluon Start is a website which enables you to generate the structure and skeleton of your Java client application. You can configure the following properties of the application:

  • Name, groupId and artifactId to use for the generated Maven project

  • JavaFX version and modules required by the project

  • Gluon Mobile stack to include in the project. For example, Glisten (for the UI toolkit) and Gluon Maps can be selected to be used in the same project

  • Gluon Attach services required in the project

  • If the application needs a flexible and secure way to connect to an existing or new backend or cloud service, Gluon CloudLink can be selected as well

All options are easily selectable and Gluon Start will generate the project with correct dependencies for you.

Unzip the project and run it via the terminal. Or, import it directly into your favorite IDE.

2.3. IDE Plugins

Another way to get started quickly is using one of our IDE plugins. The plugin aids in creating a Gluon application project inside your IDE. Although, the IDE plugins are not required to develop Gluon applications in your IDE. At present, we offer IDE plugins to bootstrap your Gluon applications for NetBeans and IntelliJ IDEA.

You can read more about the IDE plugins in the IDE Support section of the documentation.

You can still use other IDEs to develop your Gluon application, projects can be created from Gluon Start and directly opened in your favorite IDE.

2.4. Maven Archetype

A simple Gluon application can be created by using the GluonFX Maven Archetype for simple Gluon Mobile application.

For example:

mvn archetype:generate \
        -DarchetypeGroupId=com.gluonhq \
        -DarchetypeArtifactId=gluonfx-archetype-mobile \
        -DarchetypeVersion=0.0.3 \
        -DgroupId=com.gluonhq  \
        -DartifactId=gluon-mobile-sample \
        -Dversion=1.0.0-SNAPSHOT \
        -Djavafx-version=15

2.5. Adding your license

Some Gluon components require a license in certain circumstances. Check https://gluonhq.com/pricing/ for more information

After you have created the project or downloaded an existing one, in order to include your license, you need to add a file called gluonmobile.license to your resources and paste your valid license key as the content:

src/main/resources/gluonmobile.license

XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

This method allows you to keep your license private as long as you exclude it from your source version control system.

Licenses are validated online once per application install.

If for some reason the license service can’t be contacted, your end-users won’t be annoyed by the popup, but the license check will be retried each time the application starts until successful.

2.6. IDE Live templates

A live template specifies an abbreviation which auto-completes a large chunk of code for you. Live templates for IntelliJ IDEA IDE has been defined to create JavaFX properties, including getters, setters, and the property method. It supports all property types, and both read-only and read/write properties.

The template can be downloaded from here. Once the file has been downloaded, simply do the following steps to import them into your IntelliJ IDEA:

  • Unzip the file and save settings.jar on the file system

  • In IntelliJ IDEA, click on the ‘File’ → ‘Import Settings…’

  • Locate settings.jar file on your file system

  • Restart IntelliJ IDEA, when prompted

Once the IDE has been restarted, from within your editor, you may simply type ‘fxprop‘, and a popup lets you choose the type of property you want. Once you press tab (or enter), the code for property is generated inside the editor. You can then immediately start typing the property name, and this will automatically update all the method names. Once you’ve done this, all you need to do is import the relevant classes.

2.7. What is New

Below are the most relevant changes in the last releases of Gluon Substrate, and the GluonFX Maven and Gradle plugins.

For a complete list of changes, please check Substrate releases, GluonFX Maven plugin releases and GluonFX Gradle plugin releases.

Date: Dec 6, 2023

Release: 1.0.22

Major changes

  • Add support for shared libraries on Windows

  • Add support to JDK 21 for desktop, using the official GraalVM builds with their new versioning system

  • Bug fixes

Date: Sep 14, 2023

Release: 1.0.21

Major changes

  • Bump Java SDK static version

  • Bump Android SDK to 33

  • Release Attach 4.0.19

  • Use Gradle’s built-in Java Module System support

  • Bug fixes

Date: Jun 26, 2023

Release: 1.0.19

Major changes

  • Bump JavaFX static version to 21-ea+11.1

  • Allow running with the official GraalVM builds with new versioning system

  • Fix missing CAP cache files for iOS-Simulator

Date: Mar 21, 2023

Release: 1.0.18

Major changes

  • JavaFX 20 is released. Note that starting with JavaFX 20, JDK 17+ is required

  • Bump JavaFX static version to 21-ea+9.1

  • Android SDK/NDK are installed from latest Android SDK manager

Date: Mar 21, 2023

Release: 1.0.17

Major changes

  • Bump JavaFX version to 21-ea+5. Note that starting with JavaFX 20, JDK 17+ is required

  • Maven plugin: Maven runtime 3.9.0 is not supported yet (an error will be thrown asking to downgrade to 3.8.8 or lower, in that case)

  • Maven plugin: new goal allows building static libraries for iOS, via gluonfx:staticlib

  • iOS: update ios-deploy to 1.12.1

  • Android/iOS: Update CAP cache files, which allows using JDK 19

  • Android: OS handles volume buttons

  • Release Attach 4.0.17 with the Audio service implementation for iOS, and improvements for Android.

  • Bug fixes

Date: Nov 16, 2022

Release: 1.0.16

Major changes

  • Bump JavaFX version to 20-ea+7. Note that starting with JavaFX 20, JDK 17+ is required

  • iOS simulator: default to iPhone 14

  • Android: Use of Android API level 31, added support for long press gestures

  • Release Attach 4.0.16

  • Bug fixes

Date: Aug 8, 2022

Release: 1.0.15

Major changes

  • Added support for shared libraries, via the new gluonfx:sharedlib goal

  • Android: Added support of Android NDK 23+

  • Release Attach 4.0.15

  • Bug fixes

Date: Jun 13, 2022

Release: 1.0.14

Major changes

  • Released Gluon’s GraalVM builds 22.1.0.1 for JDK 11 and JDK 17, with support for Apple Silicon chip

  • Android fixes: the temp dir folder issue with JDK 17, and the black screen after resuming the app and changing orientation have been fixed

  • Bump JavaFX version to 19-ea+8

  • Add support for Attach' StoreReview service.

  • Bug fixes

Date: Apr 4, 2022

Release: 1.0.13

Major changes

  • Add support for Apple Silicon (MACOS_AARCH64 profile). In order to run GraalVM for macOS AArch64, a recent GraalVM dev build is needed.

  • Add linkerArgs option to configuration, allowing custom linker arguments

  • Android: Allow predictive text and swipe text

  • Bump JavaFX version to 19-ea+4

  • Bump clibs version to 27

  • Bug fixes

Date: Feb 3, 2022

Release: 1.0.12

Major changes

  • Released Gluon’s GraalVM builds 22.0.0.3 for JDK 11 and JDK 17

  • Add full support for iOS Simulator

  • Set LIR backend by default on iOS, instead of LLVM

  • Add icon to Windows native binary

  • Update Android SDK manager dependencies to run with JDK 17

  • Add defaults to ReleaseConfiguration

  • Bump JavaFX version to 18-ea+10

  • Bug fixes

Date: Jan 10, 2022

Release: 1.0.11

Major changes

  • Add Windows package support: .exe and .msi bundles can be created

  • Improve MacOS package support: .dmg bundles can be created

  • Update ReleaseConfiguration (Maven only)

  • Improved support for JDK17

  • Allow building for iOS without LLVM backend

  • Fix the filter for the native agent on Windows

  • Improve support for old Xcode versions

  • Bug fixes

Date: Dec 9, 2021

Release: 1.0.10

Major changes

  • Update ios-deploy formula to work with macOS 12.0

  • Bump JavaFX version to 18-ea+8

  • Fix missing cryptographic libraries to link on Windows

  • Add MacOS package support: .app and .pkg bundles can be created

  • Update ReleaseConfiguration (Maven only)

  • Allow custom entitlements on iOS

  • Bug fixes

Date: Nov 9, 2021

Release: 1.0.9

Major changes

  • Allow using GraalVM built with JDK 11 or JDK 17

  • Add support for Android .aab bundle

  • Bump JavaFX version to 18-ea+6

  • Bug fixes

Date: Oct 25, 2021

Release: 1.0.8

Major changes

  • Bump targetSDKVersion to 30 on Android

  • Use appId as bundleID on iOS

  • Bump JavaFX version to 18-ea+3

  • Bug fixes

3. IDE Support

The ideal way to create a Gluon application is via Gluon Start and importing it directly into your favorite IDE.

Gluon also provides basic IDE support via plugins for IntelliJ IDEA and Netbeans. These plugins can be used to create a basic Gluon project, with all the required dependencies to natively build the project, and ultimately run the native image.

We will briefly discuss on how to install these plugins, create a new project and build a native image using GluonFX plugin in the following section.

3.1. IntelliJ IDEA

In this section, we’ll explain briefly how to install the plugin on IntelliJ IDEA and how to use it to create a sample application that can be deployed on desktop, Android and iOS devices. Before we start, make sure to check the Platforms section for a list of prerequisites for each platform.

3.1.1. Plugin Installation

You can get it from here, or you can directly install it from IntelliJ IDEA: click File → Settings…​ (or IntelliJ IDEA → Preferences…​) and select Plugins on the left. You will see the installed plugins on your system.

Now click Marketplace, type Gluon, select the result and click Install.

Find Gluon Plugin

The Gluon Plugin will be downloaded and installed. Then press Restart IDE or simply press OK to close the plugins dialog, and restart the IDE when asked.

3.1.2. Create a new Gluon project

Once the plugin is installed, we can use it to create a sample application.

In IntelliJ IDEA, click File → New → Project…​ and select Gluon on the left, and one of the available projects on the right. For instance, Gluon Mobile - Single View Project. Press Next.

New Gluon Project

The first time you use the plugin, you will be asked to enter your email address.

Gluon Settings

If you already have a Gluon Mobile key for your projects, you can insert it here as well, so it will be added by default to your new projects. If you don’t, you will be using the free (trial) version. Please find more about Gluon Mobile licenses.

You can access these settings later on from File→Settings→Gluon (or IntelliJ IDEA→Preferences…​→Gluon):

Gluon Settings

Once you have completed this first-time-only form, click Next.

Type the package name and the main class name. Select the platforms to deploy the application, and the build tool of your choice. For this tutorial, we will use the Maven build tool. Press Next.

Package name

From the list, select a valid Java JDK (11+) and press Next.

Java JDK

Now add a name and a location for the project and press Finish.

Project name

The project will be imported and opened.

3.1.3. The Gluon Mobile project

The plugin already adds some default code in the main folder, so we’ll be able to run it without adding a single line of code.

The Maven project has a dependency on the OpenJFX Maven Plugin and the GluonFX Maven plugin to the project.

These plugins add a series of goals and to access them we just need to open Maven tool window in IntelliJ IDEA. The Maven tool window shows all the available Maven plugins along with their respective goals.

IDEA Project

3.1.4. Run the application

Make sure JAVA_HOME is properly set: from the Maven tool window, select Maven Settings→Maven→Runner, click on browse Environment variables, and if JAVA_HOME is not included, add it.

Java Home

Before creating a native image, it’s easier to run the application first and verify that there are no errors.

Select Plugins → javafx → javafx:run from the Maven tool window. Verify that all the tasks are executed without errors, and the project runs fine on your desktop. Alternatively you can open the terminal (View→Tool Windows→Terminal) and run mvn javafx:run.

Now let’s make a slight change to the code. In the BasicView class, update the text of the label on line 20 to the following:

button.setOnAction(e -> label.setText("Hello from IntelliJ IDEA!"));

Run it again to see that the new message shows up on your desktop when you click the button.

Run Project

3.1.5. Create & run a native image of the application

In order to run the native image steps it is better to use a system terminal or the embedded terminal from your IDE. However, it is still possible to use the IDE’s Maven window for this.

Make sure you set the environment variable GRAALVM_HOME. Alternatively, you can add path to GraalVM installation directory by adding graalvmHome to the gluonfx-maven-plugin configuration.

If you are running on Windows, you need to run all the gluonfx goals from an x64 terminal.

However, you can use the terminal from IntelliJ for this. Go to File→Settings…​→Tools→Terminal, and change Shell path from cmd.exe to cmd.exe /k "<path to VS2019>\VC\Auxiliary\Build\vcvars64.bat.

If you are running from IDE, make sure that the mvn executable under path/to/IntelliJ/plugins/maven/lib/maven3/bin/ has exec permissions, or select a custom Maven installation by setting the Maven home directory from the Maven tool window, Maven Settings→Maven.

GluonFX plugin provides us various goals which are explained in detail earlier in the documentation. For this tutorial, we will be using the gluonfx:build goal to create a native image of the application.

Execute mvn gluonfx:build from the terminal, or, select Plugins → gluonfx → gluonfx:build goal from the Maven tool window.

This goal typically takes a couple of minutes to complete and may vary depending on the system. Once the goal is executed successfully, the native image can be run by executing gluonfx:nativerun goal.

3.1.6. Create & install an Android native image

This part is only applicable for Linux.

Now we are ready to deploy the same application on an Android device.

Go to Maven tool window, expand Profiles and check android. Make sure that all other profiles are unchecked. This will activate the pre-existing android profile.

Run mvn -Pandroid gluonfx:build from the terminal, or alternatively, execute the gluonfx:build goal from the Maven plugins section.

Once the native image is created, we need to package it into an apk before we can install it on a physical Android device.

Execute mvn -Pandroid gluonfx:package to create an apk. Once the goal is executed successfully, connect a physical device and install the native image by executing the mvn -Pandroid gluonfx:install goal.

When the installation ends successfully, run mvn -Pandroid gluonfx:nativerun or find the application on your device and open it up:

Android app

3.1.7. Create & install an iOS native image

This part is only applicable for MacOS.

Now we are ready for deploying the same application on an iOS device.

Go to Maven tool window, expand Profiles and check ios. Make sure that all other profiles are unchecked. This will activate the pre-existing ios profile. All gluonfx goals will now target iOS platform.

Run mvn -Pios gluonfx:build from the terminal, or alternatively, execute the gluonfx:build goal from the Maven plugins section.

Once the goal is executed successfully, connect a physical device and run the native image on it by executing mvn -Pios gluonfx:nativerun.

iOS app

3.2. NetBeans

In this section, we’ll explain briefly how to install the Gluon plugin on Apache NetBeans and how to use it to create a sample application that can be deployed on desktop, Android and iOS devices. Before we start, make sure to check the Platforms section for a list of prerequisites for each platform.

3.2.1. Plugin Installation

You can get it from the Apache NetBeans plugin portal, or you can directly install it from NetBeans: click Tools → Plugins. Now select Available Plugins…​ and find Gluon Plugin.

Plugins Window

Select the plugin, click Install and follow the steps:

Install plugin

Accept the license and click Install. Click Continue when prompted:

Plugin warning

Wait until the Gluon Plugin is installed and click Finish.

Plugin installed

You will find the plugin under the Installed tab in the Plugins window.

3.2.2. Create a new Gluon project

Once the plugin is installed, we can use it to create a sample application.

In NetBeans, click File → New → Project…​ and select Gluon on the left, and one of the available projects on the right. For instance, Gluon Mobile - Single View Project. Press Next.

New Gluon Project

The first time you use the plugin, you will be asked to enter your email address.

Gluon Settings

If you already have a Gluon Mobile license key for your projects, you can insert them here as well, so they will be added by default to your new projects. If you don’t, you will be using the free (trial) version. Please find more about Gluon Mobile licenses.

You can access these settings later on from Tools→Options→Miscellaneous→Gluon (or NetBeans→Preferences…​→Miscellaneous→Gluon):

Gluon Settings

Once you have completed this first-time-only form, click Next.

Type the name of the project, find a proper location, add the package name and change the main class name if required. Select the platforms to deploy the application, and the build tool of your choice. For this tutorial, we will use the Maven build tool.

Project Settings

Press Finish and the project will be created and opened.

You will notice that a Maven project has been created with the pom containing profiles for the platforms you selected. These profiles make it easier to create native images targeted to each of these platforms.

3.2.3. The Gluon Mobile project

The plugin already adds some default code in the main folders, so we’ll be able to run it without adding a single line of code.

The Maven project has a dependency on the OpenJFX Maven Plugin and the GluonFX Maven plugin to the project.

These plugins add a series of goals and to access them we just need to switch to the Navigator panel in NetBeans. The Navigator panel shows all the available Maven goals.

NetBeans Project

3.2.4. Run the application

Make sure the Maven default JDK is properly set to 11+. Tools→Options→Java→Maven (or NetBeans→Preferences…​→Java→Maven), set Default JDK to JDK 11+ (or click Manage Java Platfoms to add a new Platform if it doesn’t exist).

Default JDK

Before creating a native image, it’s easier to run the application first and verify that there are no errors.

Select Run project (F6) or select javafx:run from the Navigator. Verify that all the tasks are executed without errors, and the project runs fine on your desktop. Alternatively you can open the terminal (Tools→Open in Terminal) and run mvn javafx:run.

Let’s make a slight change to the code. In the BasicView class, update the text of the label on line 21 to the following:

button.setOnAction(e -> label.setText("Hello from NetBeans!"));

Run it again to see that the new message shows up on your desktop when you click the button.

Run Project

3.2.5. Create & run a native image of the application

In order to run the native image steps it is better to use a system terminal or the embedded terminal from your IDE. However, it is still possible to use the IDE’s Navigator window for this.

Make sure you set the environment variable GRAALVM_HOME. Alternatively, you can add path to GraalVM installation directory by adding graalvmHome to the gluonfx-maven-plugin configuration.

If you are running on Windows, you need to run all the gluonfx goals from a x64 terminal.

However, you can use the terminal from NetBeans for this (it requires Cygwin installed). On the terminal, run once cmd.exe /k "<path to VS2019>\VC\Auxiliary\Build\vcvars64.bat.

GluonFX plugin provides us various goals which are explained in details, earlier in the documentation. For this tutorial, we will be using the gluonfx:build goal to create a native image of the application.

Execute mvn gluonfx:build from the terminal, or, run the gluonfx:build goal from the Navigator with modifiers, and add Env.GRAALVM_HOME=/path/to/GraalVM.

This goal typically takes a couple of minutes to complete and may vary depending on the system. Once the goal is executed successfully, the native image can be run by executing gluonfx:nativerun goal.

3.2.6. Create & install an Android native image

This part is only applicable for Linux.

Now we are ready for deploying the same application on an Android device.

Right-click on the project and select Set Configuration and select android. This will activate the pre-existing android profile.

Run mvn -Pandroid gluonfx:build from the terminal, or, execute the gluonfx:build goal from the Navigator with modifiers, adding profile android, and Env.GRAALVM_HOME=/path/to/GraalVM.

Once the native image is created, we need to package it into an apk before we can install it on a physical Android device.

Execute mvn -Pandroid gluonfx:package to create an apk. Once the goal is executed successfully, connect a physical device and install the native image by executing the mvn -Pandroid gluonfx:install goal.

When the installation ends successfully, run mvn -Pandroid gluonfx:nativerun or find the application on your device and open it up:

Android app

3.2.7. Create & install an iOS native image

This part is only applicable for MacOS.

Please check the prerequisites for iOS.

Now we are ready for deploying the same application on an iOS device.

Right-click on the project and select Set Configuration and select ios. This will activate the pre-existing ios profile.

Run mvn -Pios gluonfx:build from the terminal, or, execute the gluonfx:build goal from the Navigator with modifiers, adding profile ios, and Env.GRAALVM_HOME=/path/to/GraalVM.

Once the goal is executed successfully, connect a physical device and run the native image on it by executing mvn -Pios gluonfx:nativerun.

iOS app

3.3. Eclipse

In this section, we’ll explain briefly how to create and download a project from Gluon Start, open it in the Eclipse IDE and deployed on desktop, Android and iOS devices. Before we start, make sure to check the Platforms section for a list of prerequisites for each platform.

3.3.1. Project creation via Gluon Start

Create a Maven project from Gluon Start and open it in Eclipse IDE.

3.3.2. The Gluon Mobile project

The project already has some default code in the main folders, so we’ll be able to run it without adding a single line of code.

Gluon project

The Maven project has a dependency on the OpenJFX Maven Plugin and the GluonFX Maven plugin to the project. These plugins add a series of goals which will be used to run and create native images of the application.

3.3.3. Run the application

Before creating a native image, it’s easier to run the application first and verify that there are no errors.

Right-click on the project, select Run As → Maven Build…​. An "Edit Configuration" window opens. In the Goals textfield, type javafx:run and click Run. Verify that all the tasks are executed without errors, and the project runs fine on your desktop.

Show the Maven Build view

Let’s make a slight change to the code. In the BasicView class, update the text of the label on line 20 to the following:

button.setOnAction(e -> label.setText("Hello from Eclipse!"));

Run the application again to verify that the text inside the label has changed.

Run project

3.3.4. Create & run a native image of the application

In order to run the native image steps it is better to use a system terminal or the embedded terminal from your IDE. However, it is still possible to use the IDE’s Run Configurations dialog for this.

Make sure you set the environment variable GRAALVM_HOME. Alternatively, you can add path to GraalVM installation directory by adding graalvmHome to the gluonfx-maven-plugin configuration.

If you are running on Windows, you need to run all the GluonFX goals from a x64 terminal.

However, you can use the terminal from Eclipse for this (it can be installed from the Eclipse Marketplace). On the terminal, run once cmd.exe /k "<path to VS2019>\VC\Auxiliary\Build\vcvars64.bat.

GluonFX plugin has various goals which are explained in details earlier in the documentation. For this tutorial, we will be using the gluonfx:build goal to create a native image of the application.

Execute mvn gluonfx:build from the terminal, or, open the Run Configurations…​ window and update the Goal to gluonfx:build and click Run.

This goal typically takes a couple of minutes to complete and may vary depending on the system. Once the goal is executed successfully, the native image can be run by executing gluonfx:nativerun goal.

3.3.5. Create & install an Android native image

This part is only applicable for Linux.

Now we are ready for deploying the same application on an Android device.

Right-click on the project and open Maven → Select Maven Profiles…​ window. Check android checkbox and uncheck all others. This will activate the pre-existing android profile. All goals will now target android platform instead of desktop.

Run mvn -Pandroid gluonfx:build from the terminal, or alternatively, execute the gluonfx:build goal again, this time the android profile should be selected by default, else add it to the profiles field.

Once the native image is created, we need to package it into an apk before we can install it on a physical Android device.

Execute mvn -Pandroid gluonfx:package goal to create an apk. Once the goal is executed successfully, connect a physical device and install the native image by executing gluonfx:install goal.

Once the goal is executed successfully, connect a physical device and install the native image by executing the mvn -Pandroid gluonfx:install goal.

When the installation ends successfully, run mvn -Pandroid gluonfx:nativerun or find the application on your device and open it up:

Android app

3.3.6. Create & install an iOS native image

This part is only applicable for MacOS.

Please check the prerequisites for iOS.

Now we are ready for deploying the same application on an iOS device.

Right-click on the project and open Maven → Select Maven Profiles…​ window. Check ios checkbox and uncheck all others. This will activate the pre-existing ios profile. All gluonfx goals will now target iOS platform.

Run mvn -Pios gluonfx:build from the terminal, or alternatively, execute the gluonfx:build goal again, this time the ios profile should be selected by default, else add it to the profiles field.

Once the goal is executed successfully, connect a physical device and run the native image on it by executing mvn -Pios gluonfx:nativerun.

iOS app

3.4. Visual Studio Code

For VSCode, we recommend downloading a Maven project from Gluon Start and directly importing it into the IDE.

Make sure you have the Extension Pack for Java installed, which includes the required extensions to support Maven projects.

4. GluonFX plugin for Maven

Any Java(FX) project can be easily integrated with Gluon. This section details the specifics of the GluonFX Maven plugin and shows how to add it to your project, to create native applications or native shared libraries.

Open Source

GluonFX Maven plugin is open sourced, and licensed under the BSD-3 license. Its source code is hosted under Gluon organization in Github.

Release notes are available on our Github releases page.

4.1. Apply the plugin

Edit your pom file and add the plugin:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        <mainClass>your.mainClass</mainClass>
    </configuration>
</plugin>

The plugin allows some options that can be set in <configuration> to override the default settings.

At the moment, the plugin doesn’t support Maven 3.9.0+. Therefore, Maven runtime must be 3.8.8 or lower.

4.2. Goals

4.2.1. Native applications

To create a native application, GluonFX plugin introduces the following goals:

  • gluonfx:run

  • gluonfx:runagent

  • gluonfx:compile

  • gluonfx:link

  • gluonfx:build

  • gluonfx:package

  • gluonfx:install

  • gluonfx:nativerun

The following image shows a flow diagram of what the goals produce and the expected order.

GluonFX goals for native applications
gluonfx:run

Since: 1.0.0

Run the application on the JVM by resolving the appropriate Attach libraries required.

This goal requires the javafx-maven-plugin.

gluonfx:runagent

Since: 0.1.37

Runs the project on desktop, in combination with the javafx-maven-plugin, with GraalVM’s JVM (HotSpot) and with the native-image-agent to record the behavior of the Java application. It generates the configuration files for reflection, JNI, resource, proxy and serialization, that will be used by the native image generation with the above goals.

If needed, this goal should be executed before the others, and requires the user intervention to discover all reachable classes, by going through all possible scenes, views, dialogs, menus…​

This goal is not strictly needed, as the GluonFX plugin already provides a basic set of configuration files that can be modified manually. In any case, the configuration files generated by the tracing agent will be picked and merged with those generated by the plugin (as in most cases the content of both will be duplicated).

Using the agent might have some impact in the native compilation time and the binary size.

gluonfx:compile

This goal performs AOT compilation by executing the native-image command and builds the shared object file. It is a very intensive and lengthy task (several minutes, depending on your project and CPU), so it should be called only when the project is ready and runs fine on a VM.

The results will be available at target/gluonfx/$arch-$os/gvm.

Links the object file to create a native executable file or shared library.

The results will be available at target/gluonfx/$arch-$os/$AppName.

gluonfx:build

This goal simply combines gluonfx:compile and gluonfx:link.

gluonfx:package

Packages the executable or shared library into a target specific package that includes all the necessary dependencies.

The list of platform specific packages are as follows:

  • Linux - deb, rpm

  • macOS - app, pkg, dmg

  • Windows - exe, msi

  • iOS - app, ipa

  • Android - apk, aab

At the moment, Linux support is WIP.

gluonfx:install

Installs the package on the host system or attached device.

At the moment, this goal is only intended for iOS, iOS-sim, Android and Linux-AArch64.

gluonfx:nativerun

Since: 1.0.0

Runs either the executable generated by gluonfx:link on the host system or runs the application that was installed on the connected device (iOS, Android or Linux-AARch64).

4.2.2. Native shared libraries

To create a native shared library, GluonFX plugin has the following goal:

  • gluonfx:sharedlib

The following image shows a flow diagram of what the goal produce.

GluonFX goals for native shared libs
gluonfx:sharedlib

Since: 1.0.15

This goal runs internally gluonfx:compile gluonfx:link, but this time, the outcome is a native shared library and not an executable.

Intended to be added to third party projects, this goal should be applied only to pure Java libraries, that doesn’t have any UI/JavaFX code.

The created shared library will have the main method of the main Java class as its entrypoint method, if any.

At least, one static entry point method is required. You can use the @CEntryPoint annotation to specify entry point methods that should be exported and callable from C. No object types are permitted for parameters or return types; only primitive Java values, word values, and enum values are allowed. To provide the current thread’s execution context for the call, one of the parameters of the entry point method has to be of type IsolateThread or Isolate.

The results will be available at target/gluonfx/$arch-$os/$AppName.$ext.

The list of platform specific library extensions are as follows:

  • Linux - so

  • macOS - dylib

  • Windows - dll

  • iOS - so

  • Android - dylib

Optionally, you can run up front gluonfx:runagent to generate configuration files.

4.2.3. Native static libraries

To create a native static library, GluonFX plugin has the following goal:

  • gluonfx:staticlib

The following image shows a flow diagram of what the goal produce.

GluonFX goals for native static libs
gluonfx:staticlib

Since: 1.0.17

This goal runs internally gluonfx:compile gluonfx:link, but this time, the outcome is a native static library and not an executable.

The created static library will have the main method of the main Java class as its entrypoint method, if any.

At least, one static entry point method is required. You can use the @CEntryPoint annotation to specify entry point methods that should be exported and callable from C. No object types are permitted for parameters or return types; only primitive Java values, word values, and enum values are allowed. To provide the current thread’s execution context for the call, one of the parameters of the entry point method has to be of type IsolateThread or Isolate.

The results will be available at target/gluonfx/$arch-$os/$AppName.$ext.

The list of platform specific library extensions are as follows:

  • iOS: .a

Optionally, you can run up front gluonfx:runagent to generate configuration files.

4.3. Configuration

This is for advanced users.

The plugin allows some customization to modify the default settings, which are:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        <target>host</target>
        <mainClass>your.mainClass</mainClass>
        <bundlesList></bundlesList>
        <resourcesList></resourcesList>
        <reflectionList></reflectionList>
        <jniList></jniList>
        <attachList></attachList>
        <nativeImageArgs></nativeImageArgs>
        <linkerArgs></linkerArgs>
        <runtimeArgs></runtimeArgs>
        <verbose>false</verbose>
        <graalvmHome></graalvmHome>
        <javaStaticSdkVersion>11-ea+10</javaStaticSdkVersion>
        <javafxStaticSdkVersion>21-ea+11.1</javafxStaticSdkVersion>
        <enableSWRendering>false</enableSWRendering>
        <remoteHostName></remoteHostName>
        <remoteDir></remoteDir>
        <appIdentifier></appIdentifier>
        <releaseConfiguration>
            <!-- all targets -->
            <packageType></packageType>
            <description></description>
            <vendor></vendor>
            <!-- macOS -->
            <macAppStore></macAppStore>
            <macSigningUserName></macSigningUserName>
            <macAppCategory></macAppCategory>
            <!-- macOS/iOS -->
            <bundleName></bundleName>
            <bundleVersion>1.0</bundleVersion>
            <bundleShortVersion>1.0</bundleShortVersion>
            <providedSigningIdentity></providedSigningIdentity>
            <providedProvisioningProfile></providedProvisioningProfile>
            <skipSigning>false</skipSigning>
            <!-- iOS Simulator -->
            <simulatorDevice></simulatorDevice>
            <!-- Android -->
            <appLabel></appLabel>
            <versionCode>1</versionCode>
            <versionName>1.0</versionName>
            <providedKeyStorePath>${android-keystore-path}</providedKeyStorePath>
            <providedKeyStorePassword>${android-keystore-password}</providedKeyStorePassword>
            <providedKeyAlias>${android-key-alias}</providedKeyAlias>
            <providedKeyAliasPassword>${android-key-password}</providedKeyAliasPassword>
        </releaseConfiguration>
    </configuration>
</plugin>

4.3.1. target

A string that defines the target platform. The default is host, which refers to the platform that currently hosts the process. It can be set also to ios to create native images for iOS devices (Aarch64), or android to create native images for Android devices (Aarch64).

Default: host

4.3.2. bundlesList

List of fully qualified resource bundles that will be added to the default list of resource bundles. By default, the list already includes the JavaFX bundles:

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

  • com/sun/javafx/scene/control/skin/resources/controls

  • com/sun/javafx/scene/control/skin/resources/controls-nt

  • com.sun.media.jfxmedia.MediaErrors

  • com.sun.webkit.graphics.Images

  • com.sun.webkit.LocalizedStrings

  • javafx.scene.web.HTMLEditorSkin

  • com.sun.org.apache.xerces.internal.impl.msg.XMLMessages

And on Windows only:

  • com/sun/glass/ui/win/themes

For more advanced usage, read the Resource bundles section.

4.3.3. resourcesList

List of additional resource patterns or extensions that will be added to the default resource list that already includes:

  • png, jpg, jpeg, gif, bmp, ttf, raw

  • xml, fxml, css, gls, json, dat,

  • license, frag, vert, obj

We keep adding extensions to this list. Please check the source code for the latest list.

For more advanced usage, read the Resources section.

4.3.4. reflectionList

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

Note: The current list is added to a file that can be found under target/gluonfx/$arch-$os/gvm/reflectionconfig-$arch-$os.json.

For more advanced usage, read the JNI and Reflection section.

4.3.5. jniList

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

Note: The current list is added to a file that can be found under target/gluonfx/$arch-$os/gvm/jniconfig-$arch-$os.json.

For more advanced usage, read the JNI and Reflection section.

4.3.6. attachList

If you want to include Gluon Attach services to your project, you can use attachList to including the name of the services, like:

<!-- dependencies -->
<dependency>
    <groupId>com.gluonhq.attach</groupId>
    <artifactId>display</artifactId>
    <version>4.0.19</version>
</dependency>

<!-- plugin -->
<configuration>
    <attachList>
        <list>display</list>
    </attachList>
</configuration>

By default the attachVersion is 4.0.19.

After saving the changes, the dependencies for the defined services will be resolved to include those for defined target, and when the native compile goal is executed, the native services implementations will be added to the reflection and JNI lists.

Note: Attach platform implementations will be added only to the goals of the GluonFX plugin, but not to the JavaFX plugin. It is convenient to use Maven profiles to overcome this issue.

4.3.7. nativeImageArgs

List of additional arguments that will be added to the native image creation.

4.3.8. linkerArgs

List of additional arguments that will be added to the linker command to provide additional linker flags.

Since: 1.0.13

4.3.9. runtimeArgs

List of additional arguments that will be added as runtime arguments to the native image executable.

Note: These arguments are usually added to the command line when executing the image, the arguments in this list can be complementary.

4.3.10. verbose

Set to true will generate a more verbose output to the console, useful when having errors, to identify possible issues.

Also, you can get a more verbose output for these goals running with -X:

mvn -X gluonfx:compile

Default: false

Note: Regardless the verbose value, the full logs can be found under target/gluonfx/$arch-$os/gmv/log.

4.3.11. graalvmHome

Path to GraalVM installation directory. This is only required when GRAALVM_HOME is not set.

Since: 0.1.3

4.3.12. javaStaticSdkVersion

The version of the Java static libraries. These will be located under: ~/.gluon/substrate/javaStaticSdk/$javaStaticSdkVersion/$target-$arch/labs-staticjdk/lib.

Default: 11-ea+10

4.3.13. javafxStaticSdkVersion

The version of the JavaFX SDK and its static libraries. These will be located under: ~/.gluon/substrate/javafxStaticSdk/$javaStaticSdkVersion/$target-$arch/sdk/lib.

Default: 21-ea+11.1

4.3.14. Software Rendering

JavaFX applications can use software rendering when the graphics hardware on a system is insufficient to support hardware accelerated rendering. To enable software rendering set the enableSWRendering parameter to true in plugin configuration.

Note: Enabling software rendering will result in a longer compile time and larger native image.

Default: false

Since: 0.1.14

4.3.15. remoteHostName

Set the host name for remote deployment, typically to an embedded system, providing it is reachable and SSH is enabled.

Note: Useful for Linux-AArch64 target. See the embedded section.

4.3.16. remoteDir

Sets the directory where the native image will be deployed on the remote system, providing the remote host is reachable and SSH is enabled.

Note: Useful for Linux-AArch64 target. See the embedded section.

4.3.17. appIdentifier

By default, the application unique identifier is defined from the groupId and artifactId coordinates, but it can be set to something different if needed.

Note: Useful to set the package name on an Android app or the bundleId of an iOS app.

Since: 1.0.7

4.4. Config files

Some configuration options can alternatively be defined in configuration files instead of the configuration section of the plugin.

There are two non-exlusive options to create these configuration files.

4.4.1. Files from tracing agent

Running the gluonfx:runagent goal, the following files will be created automatically by a tracing agent under src/main/resources/META-INF/native-image folder:

  • jni-config.json

  • proxy-config.json

  • reflect-config.json

  • resource-config.json

  • serialization-config.json

  • predefined-classes-config.json

The files can be edited and modified to include or exclude content if needed.

The agent runs only on desktop, therefore only the classes for that host are discovered and added to the above config files. However, running on other platforms these might not be available. For that reason, the agent already filters out some of them (which are provided with the Substrate configuration files mentioned below). If needed, these files can be edited and modified manually before running the GluonFX goals.

4.4.2. Substrate config files

Alternatively, or in combination with the above, the configuration files detailed below can be used, and should be placed in a folder named src/main/resources/META-INF/substrate/config.

When building the application, the plugin will also inspect every jar dependency for the existence of these configuration files. The files in the jar should be placed in the same folder: META-INF/substrate/config.

JNI and Reflection

For every class that is defined in jniList or reflectionList, it is included in the configuration files target/gluonfx/$arch-$os/gvm/jniconfig-$arch-$os.json and target/gluonfx/$arch-$os/gvm/reflectionconfig-$arch-$os.json respectively, with a definition like the following:

[
  {
    "name" : "package.name.ClassName",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredFields" : true,
    "allPublicFields" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true
  }
]

This has an impact on both compilation time and memory footprint, because all methods and fields are opened. Ideally, it is better to open only the minimal list of fields and methods that are required for your application to run.

You can create the following files:

  • jniconfig.json and/or reflectionconfig.json: these are applied to all targets

  • jniconfig-$arch-$os.json and/or reflectionconfig-$arch-$os.json: these are only applied to targets that match both the architecture and operating system. For example, specifying reflection configuration for linux 64bit can be achieved by creating a file called reflectionconfig-x86_64-linux.json.

The files must contain the fully qualified name of classes, together with some attributes (like allDeclaredFields), methods and/or fields that will be invoked via reflection and that are not already part of the list.

For instance, the configuration below only adds the method named valueOf of the MaterialDesignIcon class to the final reflection configuration:

src/main/resources/META-INF/substrate/config/reflectionconfig.json
[
  {
    "name":"com.gluonhq.charm.glisten.visual.MaterialDesignIcon",
    "methods":[
      {"name":"valueOf","parameterTypes":["java.lang.String"] }
    ]
  }
]

For more information, see Reflection use in Native Images.

Resources

The resourcesList configuration option can also be defined in a configuration file:

  • resourceconfig.json: applied to all targets

  • resourceconfig-$arch-$os.json: only applied to targets that match the given architecture and operating system

For more information, see Accessing resources in Native Images.

For instance, the configuration below includes a pattern to load all *.txt files found:

src/main/resources/META-INF/substrate/config/resourceconfig.json
[
  {
  "resources": [
    {"pattern": ".*\\.txt$"}
  ]
  }
]
Resource bundles

The bundlesList configuration option can also be defined in a configuration file:

  • resourcebundles: applied to all targets

  • resourcebundles-$arch-$os: only applied to targets that match the given architecture and operating system

For instance, below are the fully qualified name of two given resource bundles that will be included:

src/main/resources/META-INF/substrate/config/resourcebundles
com.mycompany.myproject.mybundle1
com.mycompany.myproject.mybundle2
Build time initialization

If you need to set a class or list of classed to be initialized at build time, you can add their fully qualified name to the following configuration files:

  • initbuildtime: applied to all targets

  • initbuildtime-$arch-$os: only applied to targets that match the given architecture and operating system

This can be especially convenient for enums that have a large list of values.

For more information, see Class initialization in Native Image.

4.5. Release Configuration

These options allow setting release parameters for building installers and releasing them to stores across different platforms.

4.5.1. Common

These properties are common across various platforms:

packageType

Type of package bundle that can be generated.

On macOS, 'pkg' or 'dmg' can be selected. Note that 'app' is generated by default. On Windows, exe and msi are generated by default. On iOS 'app' and 'ipa' are generated by default. On Android 'apk' and 'aab' are generated by default.

description

A short description about the application.

Since: 1.0.12

vendor

Vendor of the application. Ideally, name of the company or individual developing the application.

Since: 1.0.11

version

Version of the application.

Since: 1.0.12

4.5.2. macOS

Only for macOS:

macAppStore

Boolean that indicates if the macOS bundle is intended for the Mac App Store.

macSigningUserName

Team or user name portion in Apple signing identities

macAppCategory

The category that best describes the app for the Mac App Store. By default it is set to public.app-category.utilities.

See LSApplicationCategoryType for the full list of categories.

4.5.3. macOS/iOS

For both macOS and iOS:

bundleName

A user-visible short name for the bundle. If not set, the Maven project’s name will be used.

bundleVersion

The version of the build that identifies an iteration of the bundle. A string composed of one to three period-separated integers, containing numeric characters (0-9) and periods only. Default 1.0.

bundleShortVersion

A user-visible string for the release or version number of the bundle. A string composed of one to three period-separated integers, containing numeric characters (0-9) and periods only. Default 1.0.

Signing for development or distribution
  • providedSigningIdentity: String that identifies a valid certificate that will be used for macOS/iOS development or macOS/iOS distribution.

  • providedProvisioningProfile: String with the name of the provisioning profile created for macOS/iOS. When not provided, the plugin will be selected from all the valid identities found installed on the machine from any of these types:

    macOS: Apple Development|Apple Distribution|Mac Developer|3rd Party Mac Developer Application|Developer ID Application
    iOS: iPhone Developer|Apple Development|iOS Development|iPhone Distribution

and that were used by the provisioning profile development or distribution of the given app. When not provided, the plugin will try to find a valid installed provisioning profile that can be used to sign the app, including wildcards.

skipSigning

Boolean that can be used to skip signing macOS/iOS apps. This will prevent any deployment, but can be useful to run tests without an actual device.

4.5.4. iOS Simulator

Only for the iOS Simulator.

simulatorDevice

A string with a valid name of an iOS simulator device. It can be found from Xcode → Window → Devices and Simulators. If not provided or invalid, a default simulator device will be used.

4.5.5. Android

appLabel

A user-visible short name for the app, if not set, the Maven project’s name will be used.

versionCode

A positive integer used as an internal version number, by default is set to 1.

versionName

A string used as the version number shown to users, like <major>.<minor>.<point>. By default is 1.0.

Signing for release:

If these 4 parameters are not set, the app will be signed for debug only.

  • providedKeyStorePath: A string with the path to a keystore file that can be used to sign the Android apk/aab bundles

  • providedKeyStorePassword: A string with the password of the provide keystore file

  • providedKeyAlias: A string with an identifying name for the key

  • providedKeyAliasPassword: A string with a password for the key

Note that it can be convenient to define the values for these options in the ~/.m2/settings.xml file, using Maven profiles.

4.6. Upgrading from previous versions

4.6.1. Upgrading from 0.1.x to 1.x.x

Client Maven plugin was renamed to GluonFX Maven plugin in v1.0.0. If you have been using the former, there are a few changes that you need to know:

  • Prefix for all goals has been renamed from client:* to gluonfx:*. For example: to build a native image, use mvn gluonfx:build instead of mvn client:build

  • gluonfx:nativerun replaces client:run to execute native application

  • A new goal gluonfx:run can be used to run the application on JVM/hotspot. It simplifies resolution of Attach libraries for Desktop. For more information, check the issue: Simplify resolution of Attach artifacts on JVM

5. Platforms

Gluon applications can run on various platforms. These applications can run directly on the JVM on all desktop and embedded platforms without any additional requirement.

Table 1. Features supported on JVM
Architecture Platform controls/fxml media web

x86

Windows

x64

Linux

Mac OS

Windows

arm32

Linux

AArch64

Linux

Mac OS

Windows

Gluon applications can also be converted to a native image (a binary or an executable) that can target a specific platform. Some native image targets have a dependency on the platform on which they can be built. For example, iOS images can currently be only produced on a MacOS.

In this section, we will discuss the requirements, procedure, and restrictions for development and deployment of Gluon applications across platforms. We will also try to build HelloFX, a simple JavaFX application, on each platform.

Native image builds for a specific target platform have to be created from a specific host platform (e.g. Windows native build has to be done on Windows, an iOS native build on macOS). This hurdle can be overcome by using a build automation tool like Github Actions, which provides build 'runners' for every major OS. Each platform section below contains an example of a Github Actions Workflow. You can also have a look at the Hello Gluon CI Sample which combines a workflow for all supported platforms.

Native image is only supported on 64-bit platforms.

The following matrix tables will help to list features and platforms supported across native-image using Gluon:

Table 2. Platform architecture matrix for Native Images
Platform x64 AArch64

Windows

Mac OS

Linux

Android

iOS

Table 3. Platform feature matrix for Native Images
Platform controls/fxml media web

Windows

Mac OS

Linux

Android

iOS

- Supported via Attach

5.1. Linux

5.1.1. Pre-requisites

  • GraalVM

  • Additional packages (more information on this later)

GraalVM

Gluon applications can run on JVM without any additional requirement. However, to create native images for your Gluon application, you will need the latest version of the Gluon built version of GraalVM.

The latest version of Gluon’s GraalVM for Linux can be found at https://github.com/gluonhq/graal/releases/latest.

Download the graalvm-svm-java17-linux-gluon-22.1.0.1-Final.zip file, unzip and extract to a proper location, and finally set the GRAALVM_HOME environment variable to point to the GraalVM directory:

export GRAALVM_HOME=/path/to/graalvm-svm-java17-linux-gluon-22.1.0.1-Final

For convenience, you can add the above to your .bashrc file.

If needed, there is also a GraalVM build based on Java 11.

Additional packages

In addition to GraalVM, the following packages are also required:

  • gcc version 6 or higher

  • ld version 2.26 or higher

5.1.2. CentOS

Required packages

Execute the following command to install the required yum packages:

sudo yum install pkgconfig gtk3-devel libXtst-devel
CentOS 6/7: Developer Toolset

By default, CentOS 6 and 7 bundle gcc version 4.4.x and 4.8.x respectively. The easiest way to install a more recent version is to install Developer Toolset 6 or higher using Software Collections. The installation process is described in detail at: https://www.softwarecollections.org/en/scls/rhscl/devtoolset-8/

5.1.3. HelloFX Sample

Clone HelloFX on your system and run the mvn gluonfx:compile goal. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:compile (default-cli) @ hellofx ---
[INFO] ==================== COMPILE TASK ====================
[INFO] We will now compile your code for x86_64-linux-linux. This may take some time.
[INFO] [SUB] Warning: Ignoring server-mode native-image argument --no-server.
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hellofx.hellofx'...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                       (4,2s @ 0,15GB)
[INFO] [SUB] [GluonFeature] enabled for config com.oracle.svm.hosted.FeatureImpl$IsInConfigurationAccessImpl@28d6290
[INFO] [SUB] GluonFeature enabled in setup com.oracle.svm.hosted.FeatureImpl$DuringSetupAccessImpl@5f404594
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB]  1 user-provided feature(s)
[INFO] [SUB]   - com.gluonhq.substrate.feature.GluonFeature
[INFO] [SUB] [2/7] Performing analysis...  [*********]                                                  (49,6s @ 2,93GB)
[INFO] [SUB]   11.101 (88,86%) of 12.493 classes reachable
[INFO] [SUB]   19.698 (72,70%) of 27.094 fields reachable
[INFO] [SUB]   53.931 (60,97%) of 88.455 methods reachable
[INFO] [SUB]      635 classes,   109 fields, and 1.306 methods registered for reflection
[INFO] [SUB]      112 classes,   138 fields, and   182 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (3,1s @ 3,70GB)
[INFO] [SUB] [4/7] Parsing methods...      [**]                                                          (2,6s @ 2,37GB)
[INFO] [SUB] [5/7] Inlining methods...     [*****]                                                       (3,0s @ 4,06GB)
[INFO] [SUB] [6/7] Compiling methods...    [******]                                                     (33,9s @ 3,10GB)
[INFO] [SUB] [7/7] Creating image...
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]                         9,2s (8,6% of total time) in 37 GCs | Peak RSS: 6,97GB | CPU load: 8,93
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hellofx.hellofx' in 1m 46s.

And as a result, hellofx.hellofx.o is created and can be found under target/gluonfx/x86_64-linux/gvm/tmp/SVM-*/hellofx.hellofx.o.

Run the mvn gluonfx:link goal to produce the native image. As a result, target/gluonfx/x86_64-linux/hellofx is created. It can be executed directly or with mvn gluonfx:nativerun.

HelloFX Linux Link Output
HelloFX Linux running

5.1.4. Linux native builds using Github Actions

Using this Github workflow, you can checkout, build, and upload the native binary as a build artifact. The steps are described in code comments:

jobs:
build:
    runs-on: ubuntu-latest
    steps:
      # Checkout your code
      - uses: actions/checkout@v2

      # Make sure the latest GraalVM is installed.
      # after this step env.JAVA_HOME and env.GRAALVM_HOME will point to the GraalVM location
      - name: Setup GraalVM built by Gluon
        uses: gluonhq/setup-graalvm@master
        # set GITHUB_TOKEN to avoid exceeding GitHub's API rate limit
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Install extra required packaged on top of ubuntu-latest
      - name: Install libraries
        run: sudo apt install libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libgl-dev libgtk-3-dev libpango1.0-dev libxtst-dev

      # Create a staging directory where the binary will be copied into
      - name: Make staging directory
        run: mkdir staging

      # Install the Gluon License (optional)
      # Using this step requires you to set a GLUON_LICENSE secret in the secret configuration of your repo. Have a look at https://gluonhq.com/products/mobile/buy for more information about obtaining a license.
      - name: Gluon License
        uses: gluonhq/gluon-build-license@v1
        with:
          gluon-license: ${{ secrets.GLUON_LICENSE }}


      # Build your project using Maven
      # The desktop profile is used, which means a native build will be created for the host platform (in this case Linux) itself.
      - name: Gluon Build
        run: mvn -Pdesktop gluonfx:build gluonfx:package

      # Copy the native binary to the staging directory
      - name: Copy native image to staging
        run: cp -r target/gluonfx/x86_64-linux/HelloGluon staging

      # Upload the staging directory as a build artifact. You will be able to download this after the build finishes.
      - name: Upload
        uses: actions/upload-artifact@v2
        with:
          name: Package
          path: staging

You can see this workflow in action in the Hello Gluon CI Sample.

5.1.5. HelloSharedLib Sample

Clone HelloSharedLib on your system and run the mvn gluonfx:sharedlib goal. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:sharedlib (default-cli) @ hellosharedlib ---
[INFO] ==================== SHARED LIBRARY TASK ====================
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hello.hellosharedlib' (shared library)...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                       (3.8s @ 0.21GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB]  C compiler: gcc (linux, x86_64, 9.4.0)
[INFO] [SUB]  Garbage collector: Serial GC
[INFO] [SUB]  1 user-provided feature(s)
[INFO] [SUB]   - com.gluonhq.substrate.feature.GluonFeature
[INFO] [SUB] [2/7] Performing analysis...  [**********]                                                 (24.6s @ 1.33GB)
[INFO] [SUB]    6,015 (86.68%) of  6,939 classes reachable
[INFO] [SUB]    7,634 (53.13%) of 14,368 fields reachable
[INFO] [SUB]   27,299 (55.45%) of 49,231 methods reachable
[INFO] [SUB]      376 classes,   105 fields, and 1,030 methods registered for reflection
[INFO] [SUB]       83 classes,    75 fields, and    97 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (1.9s @ 1.77GB)
[INFO] [SUB] [4/7] Parsing methods...      [*]                                                           (1.8s @ 2.34GB)
[INFO] [SUB] [5/7] Inlining methods...     [****]                                                        (1.8s @ 2.07GB)
[INFO] [SUB] [6/7] Compiling methods...    [****]                                                       (17.4s @ 1.86GB)
[INFO] [SUB] [7/7] Creating image...                                                                     (3.8s @ 2.50GB)
[INFO] [SUB]   12.27MB (34.80%) for code area:   17,786 compilation units
[INFO] [SUB]   20.26MB (57.47%) for image heap:   4,030 classes and 186,828 objects
[INFO] [SUB]    2.72MB ( 7.72%) for other data
[INFO] [SUB]   35.25MB in total
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]                         4.2s (7.1% of total time) in 25 GCs | Peak RSS: 5.09GB | CPU load: 8.62
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] Produced artifacts:
[INFO] [SUB]  /gluon-samples/HelloSharedLib/target/gluonfx/x86_64-linux/gvm/HelloSharedLib/hello.hellosharedlib.h (header)
[INFO] [SUB]  /gluon-samples/HelloSharedLib/target/gluonfx/x86_64-linux/gvm/HelloSharedLib/graal_isolate.h (header)
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hello.hellosharedlib' in 58.5s.

And as a result, HelloSharedLib.so is created and can be found under target/gluonfx/x86_64-linux/.

To test the shared library, from the HelloSharedLib’s root, run ./run.sh to compile and run the sample sample/example.cpp that makes use of the library:

The expected output is:

Sum: 3
Diff: -1
Text: Hello from Java

5.2. Mac OS

Gluon applications can run on JVM and as native image on macOS with Intel chip (x86_64) and with Apple Silicon chip (M1, AArch64).

5.2.1. Pre-requisites

  • GraalVM

  • Xcode

GraalVM

Gluon applications can run on JVM without any additional requirement. However, to create native-images for your Gluon application, you will need the latest version of the Gluon built version of GraalVM.

The latest version of Gluon’s GraalVM for Mac OS X can be found at https://github.com/gluonhq/graal/releases/latest.

x86_64

If you have Intel, download the graalvm-svm-java17-darwin-gluon-22.1.0.1-Final.zip file, unzip and extract to a proper location, and finally set the GRAALVM_HOME environment variable to point to the GraalVM directory:

export GRAALVM_HOME=/path/to/graalvm-svm-java17-darwin-gluon-22.1.0.1-Final/Contents/Home
AArch64

If you have an M1, Download the graalvm-svm-java17-darwin-m1-gluon-22.1.0.1-Final.zip file, unzip and extract to a proper location, and finally set the GRAALVM_HOME environment variable to point to the GraalVM directory:

export GRAALVM_HOME=/path/to/graalvm-svm-java17-darwin-m1-gluon-22.1.0.1-Final/Contents/Home

For convenience, you can add the above to your .bash_profile file.

If needed, in both cases there is also a GraalVM build based on Java 11.

Xcode

Xcode version 11 or higher is required and can be installed from the Mac App Store. Once installed, open it and accept the license terms.

5.2.2. HelloFX Sample

Clone HelloFX on your system and run the mvn gluonfx:compile goal. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:compile (default-cli) @ hellofx ---
[INFO] ==================== COMPILE TASK ====================
[INFO] We will now compile your code for x86_64-apple-darwin. This may take some time.
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hellofx.hellofx'...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                       (5.6s @ 0.14GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB] [2/7] Performing analysis...  [***********]                                                (98.4s @ 1.62GB)
[INFO] [SUB]   11,219 (89.17%) of 12,582 classes reachable
[INFO] [SUB]   20,077 (73.01%) of 27,498 fields reachable
[INFO] [SUB]   54,360 (61.36%) of 88,599 methods reachable
[INFO] [SUB]      651 classes,   109 fields, and 1,316 methods registered for reflection
[INFO] [SUB]      115 classes,   105 fields, and   192 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (4.8s @ 2.49GB)
[INFO] [SUB] [4/7] Parsing methods...      [**]                                                          (4.6s @ 3.81GB)
[INFO] [SUB] [5/7] Inlining methods...     [*****]                                                      (17.5s @ 2.06GB)
[INFO] [SUB] [6/7] Compiling methods...    [*******]                                                    (50.7s @ 3.88GB)
[INFO] [SUB] [7/7] Creating image...
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]                 19.7s (10.1% of total time) in 37 GCs | Peak RSS: 6.22GB | CPU load: 5.44
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hellofx.hellofx' in 3m 15s.

And as a result, hellofx.hellofx.o is created and can be found under target/gluonfx/x86_64-darwin/gvm/tmp/SVM-*/hellofx.hellofx.o.

Run the mvn gluonfx:link goal to produce the native image. As a result, target/gluonfx/x86_64-darwin/hellofx is created. It can be executed directly or with mvn gluonfx:nativerun.

HelloFX macOS Link Output
HelloFX macOS running

Note that the same procedure applies when building a native image on M1.

Optionally, you can create an app bundle and a pkg or dmg bundle, by running mvn gluonfx:package:

[INFO] --- gluonfx-maven-plugin:1.0.22:package (default-cli) @ hellofx ---
[INFO] ==================== PACKAGE TASK ====================
[INFO] Building app bundle: HelloFX/target/gluonfx/x86_64-darwin/HelloFX.app
[INFO] App bundle built successfully at: HelloFX/target/gluonfx/x86_64-darwin/HelloFX.app
[INFO] Building pkg for HelloFX/target/gluonfx/x86_64-darwin/HelloFX.app
[INFO] Pkg built successfully at HelloFX/target/gluonfx/x86_64-darwin/HelloFX-1.0.0.pkg
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
HelloFX macOS bundles
HelloFX macOS PKG running

The bundles can be signed or not, and the pkg can be signed for distribution through the Mac App Store, based on the release configuration settings.

5.2.3. Mac OS native builds using Github Actions

Using this Github workflow, you can checkout, build, and upload the native binary as a build artifact. The steps are described in code comments:

jobs:
  build:
    runs-on: macos-latest
    steps:
      # Checkout your code
      - uses: actions/checkout@v2

      # Make sure the latest GraalVM is installed.
      # after this step env.JAVA_HOME and env.GRAALVM_HOME will point to the GraalVM location
      - name: Setup GraalVM built by Gluon
        uses: gluonhq/setup-graalvm@master
        # set GITHUB_TOKEN to avoid exceeding GitHub's API rate limit
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Create a staging directory where the binary will be copied into
      - name: Make staging directory
        run: mkdir staging

      # Install the Gluon License (optional)
      # Using this step requires you to set a GLUON_LICENSE secret in the secret configuration of your repo. Have a look at https://gluonhq.com/products/mobile/buy for more information about obtaining a license.
      - name: Gluon License
        uses: gluonhq/gluon-build-license@v1
        with:
          gluon-license: ${{ secrets.GLUON_LICENSE }}

      # Build your project using Maven
      # The desktop profile is used, which means a native build will be created for the host platform (in this case Mac OS) itself.
      - name: Gluon Build
        run: mvn -Pdesktop gluonfx:build gluonfx:package

      # Copy the native binary to the staging directory
      - name: Copy native image to staging
        run: cp -r target/gluonfx/x86_64-darwin/HelloGluon* staging

      # Upload the staging directory as a build artifact. You will be able to download this after the build finishes.
      - name: Upload
        uses: actions/upload-artifact@v2
        with:
          name: Package
          path: staging

You can see this workflow in action in the Hello Gluon CI Sample.

So far this only applies for x86_64, since GitHub Actions doesn’t host a runner for AArch64 yet. In the meantime you can run it on a self hosted runner.

5.2.4. HelloSharedLib Sample

Clone HelloSharedLib on your system and run the mvn gluonfx:sharedlib goal. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:sharedlib (default-cli) @ hellosharedlib ---
[INFO] ==================== SHARED LIBRARY TASK ====================
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hello.hellosharedlib' (shared library)...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                       (9.3s @ 0.18GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB]  C compiler: cc (apple, arm64, 13.1.6)
[INFO] [SUB]  Garbage collector: Serial GC
[INFO] [SUB] [2/7] Performing analysis...  [**************]                                             (11.2s @ 2.08GB)
[INFO] [SUB]    6,017 (87.18%) of  6,902 classes reachable
[INFO] [SUB]    7,628 (53.41%) of 14,282 fields reachable
[INFO] [SUB]   27,237 (55.63%) of 48,958 methods reachable
[INFO] [SUB]      384 classes,   107 fields, and 1,038 methods registered for reflection
[INFO] [SUB]       83 classes,    76 fields, and    97 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (0.9s @ 2.53GB)
[INFO] [SUB] [4/7] Parsing methods...      [*]                                                           (1.4s @ 0.61GB)
[INFO] [SUB] [5/7] Inlining methods...     [****]                                                        (0.7s @ 2.45GB)
[INFO] [SUB] [6/7] Compiling methods...    [***]                                                         (6.4s @ 1.30GB)
[INFO] [SUB] [7/7] Creating image...                                                                     (2.5s @ 2.09GB)
[INFO] [SUB]   11.95MB (34.88%) for code area:   17,738 compilation units
[INFO] [SUB]   20.25MB (59.08%) for image heap:   4,026 classes and 188,899 objects
[INFO] [SUB]    2.07MB ( 6.04%) for other data
[INFO] [SUB]   34.27MB in total
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]                         2.4s (7.0% of total time) in 24 GCs | Peak RSS: 5.01GB | CPU load: 4.81
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] Produced artifacts:
[INFO] [SUB]  /gluon-samples/HelloSharedLib/target/gluonfx/aarch64-darwin/gvm/HelloSharedLib/graal_isolate.h (header)
[INFO] [SUB]  /gluon-samples/HelloSharedLib/target/gluonfx/aarch64-darwin/gvm/HelloSharedLib/hello.hellosharedlib.h (header)
[INFO] [SUB] ========================================================================================================================
[INFO] [SUB] Finished generating 'hello.hellosharedlib' in 34.0s.

And as a result, HelloSharedLib.dylib is created and can be found under target/gluonfx/$os-darwin/.

To test the shared library, from the HelloSharedLib’s root, run ./run.sh to compile and run the sample sample/example.cpp that makes use of the library:

The expected output is:

Sum: 3
Diff: -1
Text: Hello from Java

5.3. Windows

5.3.1. Pre-requisites

  • GraalVM

  • Microsoft Visual Studio

  • WiX Toolset (optional)

GraalVM

Gluon applications can run on JVM without any additional requirement. However, to create native-images for your Gluon application, you will need the latest version of the Gluon built version of GraalVM.

The latest version of Gluon’s GraalVM for Windows can be found at https://github.com/gluonhq/graal/releases/latest.

Download the graalvm-svm-java17-windows-gluon-22.1.0.1-Final.zip file, unzip and extract to a proper location, and finally set the GRAALVM_HOME environment variable to point to the GraalVM directory:

set GRAALVM_HOME=C:\path\to\graalvm-svm-java17-windows-gluon-22.1.0.1-Final

For convenience, you can add GRAALVM_HOME to the Environment Variables list (Advanced system settings).

If needed, there is also a GraalVM build based on Java 11.

Microsoft Visual Studio

In addition to GraalVM, Microsoft Visual Studio 2019 is also required. The Community Edition is sufficient and can be downloaded from https://visualstudio.microsoft.com/downloads/.

Please make sure to choose the English Language Pack. GraalVM might not be able to detect the installed compiler version in other languages.

During the installation process, make sure to select at least the following individual components:

  • Choose the English Language Pack

  • C++/CLI support for v142 build tools (14.25 or later)

  • MSVC v142 - VS 2019 C++ x64/x86 build tools (v14.25 or later)

  • Windows Universal CRT SDK

  • Windows 10 SDK (10.0.19041.0 or later)

All build commands, be it with Maven or Gradle, must be executed in a Visual Studio 2019 Command Prompt called x64 Native Tools Command Prompt for VS 2019. A shortcut can be found in the "Start Menu", or you can search the application in the search box. Read the Microsoft documentation for more information.

Alternatively, you can run cmd.exe /k "<path to VS2019>\VC\Auxiliary\Build\vcvars64.bat from any other terminal before you can start using the build commands.

Wix

In order to create a MSI installer for the native application, WiX 3.0 or later is required.

5.3.2. HelloFX Sample

Clone HelloFX on your system and run the mvn gluonfx:compile goal. It produces the following output:

On a Windows machine, run the gluonfx:compile goal. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:compile (default-cli) @ hellofx ---
[INFO] ==================== COMPILE TASK ====================
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hellofx.hellofx'...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                      (18,9s @ 0,14GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB] [2/7] Performing analysis...  [************]                                               (46,4s @ 4,11GB)
[INFO] [SUB]   11.120 (89,19%) of 12.468 classes reachable
[INFO] [SUB]   19.946 (73,05%) of 27.303 fields reachable
[INFO] [SUB]   54.372 (61,51%) of 88.393 methods reachable
[INFO] [SUB]      651 classes,   106 fields, and 1.310 methods registered for reflection
[INFO] [SUB]      134 classes,   150 fields, and   206 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (2,6s @ 2,23GB)
[INFO] [SUB] [4/7] Parsing methods...      [*]                                                           (1,8s @ 3,35GB)
[INFO] [SUB] [5/7] Inlining methods...     [*****]                                                       (5,0s @ 1,13GB)
[INFO] [SUB] [6/7] Compiling methods...    [*****]                                                      (23,2s @ 1,75GB)
[INFO] [SUB] [7/7] Creating image...
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]              8,7s (8,3% of total time) in 36 GCs | Peak RSS: 6,71GB | CPU load: 7,76
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hellofx.hellofx' in 1m 44s.

And as a result, hellofx.hellofx.obj is created and can be found under target\gluonfx\x86_64-windows\gvm\tmp\SVM-*\hellofx.hellofx.obj.

Run the mvn gluonfx:link goal to produce the native image. target\gluonfx\x86_64-windows\hellofx.exe is created. It can be executed directly or with mvn gluonfx:nativerun.

HelloFX Windows Link Output
HelloFX Windows running

Optionally, you can create a MSI installer, by running mvn gluonfx:package:

[INFO] --- gluonfx-maven-plugin:1.0.22:package (default-cli) @ hellofx ---
[INFO] ==================== PACKAGE TASK ====================
[INFO] Building exe for HelloFX\target\gluonfx\x86_64-windows\HelloFX.exe
[INFO] Default icon.ico image generated in HelloFX\target\gluonfx\x86_64-windows\gensrc\windows\assets.
Consider copying it to HelloFX\src\windows before performing any modification
[INFO] MSI created successfully at HelloFX\target\gluonfx\x86_64-windows\HelloFX-1.0.0.msi
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
HelloFX Windows bundle
HelloFX Windows bundle run

5.3.3. Windows native builds using Github Actions

Using this Github workflow, you can checkout, build, and upload the native binary as a build artifact. The steps are described in code comments:

  build:
    runs-on: windows-latest
    steps:
      # Checkout your code
      - uses: actions/checkout@v2

      # Setup the Windows build environment
      - name: Add msbuild to PATH
        uses: microsoft/setup-msbuild@v1.0.2

      - name: Visual Studio shell
        uses: egor-tensin/vs-shell@v1

      # Make sure the latest GraalVM is installed.
      # after this step env.JAVA_HOME and env.GRAALVM_HOME will point to the GraalVM location
      - name: Setup GraalVM built by Gluon
        uses: gluonhq/setup-graalvm@master
        # set GITHUB_TOKEN to avoid exceeding GitHub's API rate limit
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Create a staging directory where the binary will be copied into
      - name: Make staging directory
        run: mkdir staging

      # Install the Gluon License (optional)
      # Using this step requires you to set a GLUON_LICENSE secret in the secret configuration of your repo. Have a look at https://gluonhq.com/products/mobile/buy for more information about obtaining a license.
      - name: Gluon License
        uses: gluonhq/gluon-build-license@v1
        with:
          gluon-license: ${{ secrets.GLUON_LICENSE }}

      # Build your project using Maven
      # The desktop profile is used, which means a native build will be created for the host platform (in this case Windows) itself.
      - name: Gluon Build
        run: mvn -Pdesktop gluonfx:build gluonfx:package

      # Copy the native binary to the staging directory
      - name: Copy native image to staging
        run: cp -r target/gluonfx/x86_64-windows/HelloGluon.exe staging

      # Upload the staging directory as a build artifact. You will be able to download this after the build finishes.
      - name: Upload
        uses: actions/upload-artifact@v2
        with:
          name: Package
          path: staging

You can see this workflow in action in the Hello Gluon CI Sample.

5.3.4. HelloSharedLib Sample

Clone HelloSharedLib on your system and run the mvn gluonfx:sharedlib goal. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:sharedlib (default-cli) @ hellosharedlib ---
[INFO] ==================== SHARED LIBRARY TASK ====================
[INFO] [SUB] ========================================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hello.hellosharedlib' (shared library)...
[INFO] [SUB] ========================================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                                    (5,8s @ 0,18GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB]  C compiler: cl.exe (microsoft, x64, 19.27.29111)
[INFO] [SUB]  Garbage collector: Serial GC
[INFO] [SUB] [2/7] Performing analysis...  [**************]                                                          (24,4s @ 1,24GB)
[INFO] [SUB]    6.005 (87,35%) of  6.875 classes reachable
[INFO] [SUB]    7.602 (52,97%) of 14.351 fields reachable
[INFO] [SUB]   27.415 (55,88%) of 49.060 methods reachable
[INFO] [SUB]      398 classes,   102 fields, and 1.038 methods registered for reflection
[INFO] [SUB]       99 classes,    81 fields, and   108 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                               (1,5s @ 1,65GB)
[INFO] [SUB] [4/7] Parsing methods...      [*]                                                                        (1,3s @ 2,22GB)
[INFO] [SUB] [5/7] Inlining methods...     [****]                                                                     (2,2s @ 1,22GB)
[INFO] [SUB] [6/7] Compiling methods...    [****]                                                                    (15,8s @ 2,61GB)
[INFO] [SUB] [7/7] Creating image...                                                                                  (2,8s @ 3,22GB)
[INFO] [SUB]   12,84MB (36,28%) for code area:   17.914 compilation units
[INFO] [SUB]   20,44MB (57,74%) for image heap:   4.042 classes and 188.995 objects
[INFO] [SUB]    2,11MB ( 5,97%) for other data
[INFO] [SUB]   35,39MB in total
[INFO] [SUB] ------------------------------------------------------------------------------------------------------------------------
[INFO] [SUB]                         4,7s (8,2% of total time) in 25 GCs | Peak RSS: 4,62GB | CPU load: 7,10
[INFO] [SUB] ------------------------------------------------------------------------------------------------------------------------
[INFO] [SUB] Produced artifacts:
[INFO] [SUB]  \gluon-samples\HelloSharedLib\target\gluonfx\x86_64-windows\gvm\HelloSharedLib\hello.hellosharedlib.dll (shared_lib)
[INFO] [SUB]  \gluon-samples\HelloSharedLib\target\gluonfx\x86_64-windows\gvm\HelloSharedLib\graal_isolate.h (header)
[INFO] [SUB]  \gluon-samples\HelloSharedLib\target\gluonfx\x86_64-windows\gvm\HelloSharedLib\hello.hellosharedlib.h (header)
[INFO] [SUB]  \gluon-samples\HelloSharedLib\target\gluonfx\x86_64-windows\gvm\HelloSharedLib\hello.hellosharedlib.lib (import_lib)
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hello.hellosharedlib' in 56.3s.

And as a result, HelloSharedLib.dll is created and can be found under target/gluonfx/x86_64-windows/.

To test the shared library, from the HelloSharedLib’s root, run run.bat to compile and run the sample sample/example.cpp that makes use of the library:

The expected output is:

/out:example.exe
target\gluonfx\x86_64-windows\HelloSharedLib.lib
/out:target\gluonfx\x86_64-windows\example.exe
Sum: 3
Diff: -1
Text: Hello from Java

5.4. Android

5.4.1. Pre-requisites

  • Pre-requisites of Linux Platform

  • Android SDK and NDK (optional, more information on this later)

Currently, Android can be built only on Linux OS or from Windows WSL2. Alternatively you can use a GitHub Actions workflow.

5.4.2. Android development

In addition to GraalVM for Linux OS, Android SDK/NDK is required to build applications for the android platform.

Both SDK/NDK will be downloaded automatically by the "GluonFX plugin" and configured with the required packages.

But if you already have a local installation of the Android SDK/NDK, you can override this behaviour by defining environment variables:

  • ANDROID_SDK: points to the Android SDK folder on your system

  • ANDROID_NDK: points to the Android NDK folder on your system

Please make sure you have installed the following required packages:

  • platform-tools

  • platforms;android-33

  • build-tools;33.0.0

  • ndk-bundle

  • extras;android;m2repository

  • extras;google;m2repository

To target android devices, <target>android</target> needs to be added to the GluonFX plugin configuration:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        <target>android</target>
        <mainClass>${mainClassName}</mainClass>
    </configuration>
</plugin>

Alternatively, a Maven profile can be used:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        <target>${gluonfx.target}</target>
        <mainClass>${mainClassName}</mainClass>
    </configuration>
</plugin>
<profiles>
    <profile>
        <id>android</id>
        <properties>
            <gluonfx.target>android</gluonfx.target>
        </properties>
    </profile>
</profiles>

The project can be built using mvn -Pandroid gluonfx:build. This will run the compilation phase and link the compiled objects into an android executable.

5.4.3. Android packaging and distribution

Build your application

Run mvn -Pandroid gluonfx:package to generate an Android Application Package (APK) that can be installed on any Android device, and also the Android Application Bundle (AAB) that can be submitted to Google Play.

By default, the debug profile will be used to sign the APK/AAB bundles. This allows testing the APK bundle on a local device (see run on device) but not publishing the AAB bundle to a store, which requires a release profile.

To sign the AAB bundle with the correct signing key and keystore, use the releaseConfiguration settings:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    ...
    <configuration>
        ...
        <releaseConfiguration>
            <versionCode>1</versionCode>
            <providedKeyStorePath>${android-keystore-path}</providedKeyStorePath>
            <providedKeyStorePassword>${android-keystore-password}</providedKeyStorePassword>
            <providedKeyAlias>${android-key-alias}</providedKeyAlias>
            <providedKeyAliasPassword>${android-key-password}</providedKeyAliasPassword>
        </releaseConfiguration>
...
   </configuration>
</plugin>
versionCode is the unique build number of your app. You need to increase it for every upload to the Play Store.

Please follow these steps in the Android documentation to create a keystore.

Override default Android setting

If you need to set specific Android settings in AndroidManifest.xml, copy the file from target/gluonfx/aarch64-android/gensrc/android/AndroidManifest.xml to

src/android/AndroidManifest.xml
Override the default icon

By default, an icon is generated in target/gluonfx/aarch64-android/gensrc/android/rest/. If you’re distributing your app, you most certainly will want to use a custom icon.

Please refer to the Android docs for more information.

However, if you would just like to override the icon, there are nice online generators as well (e.g. https://appicon.co/)

To use the generated icon follow these steps:

  • then remove src/android/res

  • then copy the generated Android icon files (starting with mipmap-) to src/android/res

Run on device
"USB debugging" must be enabled on the connected device. To enable USB debugging follow the steps listed here.

To install the application to a connected android device, run mvn -Pandroid gluonfx:install.

Finally, you can call mvn -Pandroid gluonfx:nativerun to launch the application on the device. GluonFX plugin will also start adb logcat to print out debugging information from the device to the console.

5.4.4. HelloFX Sample

This requires an Android device that has to be plugged in at the run phase.

Clone HelloFX on a Linux system. Use the android profile, and run mvn -Pandroid gluonfx:compile. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:compile (default-cli) @ hellofx ---
[INFO] ==================== COMPILE TASK ====================
[INFO] We will now compile your code for aarch64-linux-android. This may take some time.
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hellofx.hellofx'...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                       (3.5s @ 0.13GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB] [2/7] Performing analysis...  [*********]                                                  (52.7s @ 2.46GB)
[INFO] [SUB]   11,180 (88.98%) of 12,565 classes reachable
[INFO] [SUB]   20,084 (73.33%) of 27,387 fields reachable
[INFO] [SUB]   54,292 (61.22%) of 88,680 methods reachable
[INFO] [SUB]      640 classes,   109 fields, and 1,312 methods registered for reflection
[INFO] [SUB]      109 classes,   127 fields, and   182 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (2.9s @ 3.25GB)
[INFO] [SUB] [4/7] Parsing methods...      [**]                                                          (3.4s @ 2.17GB)
[INFO] [SUB] [5/7] Inlining methods...     [*****]                                                       (2.8s @ 3.95GB)
[INFO] [SUB] [6/7] Compiling methods...    [*****]                                                      (31.2s @ 2.38GB)
[INFO] [SUB] [7/7] Creating image...
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]              9.2s (8.5% of total time) in 37 GCs | Peak RSS: 6.90GB | CPU load: 8.95
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hellofx.hellofx' in 1m 47s.

And as a result, hellofx.hellofx.o is created and can be found under target/gluonfx/aarch64-android/gvm/tmp/SVM-*/hellofx.hellofx.o.

Note that the process takes some time. There will be performance improvements, but either way, it is convenient to test first on desktop (and with HotSpot) as much as possible (i.e. with mvn javafx:run), so gluonfx:compile doesn’t have to be repeated due to avoidable errors.

Run mvn -Pandroid gluonfx:link to produce the native image. As a result, target/gluonfx/aarch64-android/libhellofx.so is created.

Finally, run mvn -Pandroid gluonfx:package to bundle the application into an Android APK that can be installed on a device and also to an Android App Bundle (AAB) that can be submitted to Google Play.

It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:package (default-cli) @ hellofx ---
[INFO] ==================== PACKAGE TASK ====================
[INFO] Default Android manifest generated in ~/Gluon/gluon-samples/HelloFX/target/gluonfx/aarch64-android/gensrc/android/AndroidManifest.xml.
Consider copying it to ~/Gluon/gluon-samples/HelloFX/src/android/AndroidManifest.xml before performing any modification
[INFO] Default Android resources generated in ~/Gluon/gluon-samples/HelloFX/target/gluonfx/aarch64-android/gensrc/android/res.
Consider copying them to ~/Gluon/gluon-samples/HelloFX/src/android/res before performing any modification
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

This creates the hellofx.apk and hellofx.aab bundles which are available at target/gluonfx/aarch64-android/gvm/.

HelloWorld Android bundles

Now we are ready to install and run the application on a plugged-in Android device. Run mvn -Pandroid gluonfx:install gluonfx:nativerun to install and launch the application on the device.

HelloFX Android Running

5.4.5. Android builds using Github Actions

The following sections show how to use Github Actions to build and release your app automatically using Github infrastructure. Using this Github workflow, you can develop your JavaFX application anywhere you like and the Github workflow will make your application available for testing in the Play Store on push.

5.4.6. Github Workflow File

Using this Github workflow, you can checkout, build, and upload the native binary to the Play Store. The steps are described in code comments and more details information can be found in the next sections.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # Checkout your project
      - uses: actions/checkout@v2

      # Make sure the latest GraalVM is installed.
      # after this step env.JAVA_HOME and env.GRAALVM_HOME will point to the GraalVM location
      - name: Setup GraalVM built by Gluon
        uses: gluonhq/setup-graalvm@master
        # set GITHUB_TOKEN to avoid exceeding GitHub's API rate limit
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Install extra requirements on top of ubuntu-latest
      - name: Install libraries
        run: |
          sudo apt-get update
          sudo apt install libasound2-dev libavcodec-dev libavformat-dev libavutil-dev libgl-dev libgtk-3-dev libpango1.0-dev libxtst-dev

      # Setup the Android keystore, based on a repo secret. See the section 'Setup Android Keystore' below.
      - name: Setup Android Keystore
        id: android_keystore_file
        uses: timheuer/base64-to-file@v1
        with:
          fileName: 'my.keystore'
          encodedString: ${{ secrets.GLUON_ANDROID_KEYSTORE_BASE64 }}

      # Install the Gluon License (optional)
      # Using this step requires you to set a GLUON_LICENSE secret in the secret configuration of your repo. Have a look at https://gluonhq.com/products/mobile/buy for more information about obtaining a license.
      - name: Gluon License
        uses: gluonhq/gluon-build-license@v1
        with:
          gluon-license: ${{ secrets.GLUON_LICENSE }}

      # Build your project using Maven
      # The android profile is used, which means a native build will be created for the gluonfx target android.
      # This step also uses some env variables taken from the repo secrets. See the section 'Setup Android Keystore' below.
      - name: Gluon Build
        run: mvn -Pandroid gluonfx:build gluonfx:package
        env:
          GLUON_ANDROID_KEYSTOREPATH: ${{ steps.android_keystore_file.outputs.filePath }}
          GLUON_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.GLUON_ANDROID_KEYSTORE_PASSWORD }}
          GLUON_ANDROID_KEYALIAS: ${{ secrets.GLUON_ANDROID_KEYALIAS }}
          GLUON_ANDROID_KEYALIAS_PASSWORD: ${{ secrets.GLUON_ANDROID_KEYALIAS_PASSWORD }}

      # Create staging directory in which the apk will be copied
      - name: Make staging directory
        run: mkdir staging

      # Copy the apk to the staging directory
      - name: Copy native images to staging
        run: cp -r target/gluonfx/aarch64-android/gvm/*.apk staging

      # Upload the staging directoy as a build artifact
      - name: Upload
        uses: actions/upload-artifact@v2
        with:
          name: Package
          path: staging

      # Upload the aab to the Google Play Store. See the section below.
      - name: Upload to Google Play
        uses: r0adkll/upload-google-play@v1.0.15
        with:
          serviceAccountJsonPlainText: ${{ secrets.GLUON_ANDROID_SERVICE_ACCOUNT_JSON }}
          packageName: com.gluonhq.samples.hellogluon
          releaseFiles: target/gluonfx/aarch64-android/gvm/HelloGluon.aab
          track: beta

You can see this workflow in action in the Hello Gluon CI Sample.

Changes to your build

Since this workflow uploads the build to the Play Store, it needs a unique buildnumber each time. If you have overriden the AndroidManifest.xml, you must increment the android:versionCode attribute. If you didn’t override AndroidManifest.xml, you can use this gluonfx configuration:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
...
    <configuration>
...
        <releaseConfiguration>
            <versionCode>${env.GITHUB_RUN_NUMBER}</versionCode>
            ...
        </releaseConfiguration>
...
   </configuration>
</plugin>

Using env.GITHUB_RUN_NUMBER, Github Actions will set the versionCode to an incremental build number.

Setup Android Keystore

Before you can upload your .aab file to the Google Play Store, you need to sign it. Please follow these steps in the Android documentation.

The result of those steps should be:

  • a keystore file: you need to base64 encode that file and configure it in the repo secret GLUON_ANDROID_KEYSTORE_BASE64

  • the keystore will have a password, configure that in the repo secret GLUON_ANDROID_KEYSTORE_PASSWORD

  • a key alias: configure that in the repo secret GLUON_ANDROID_KEYALIAS

  • that key will have a password, configure that in the repo secret GLUON_ANDROID_KEYALIAS_PASSWORD

Upload the AAB to the Google Play Store

The actual upload to the Google Play store is performed by the Github Action r0adkll/upload-google-play. Have a look at its documentation.

The most important part to configure in this case is the GLUON_ANDROID_SERVICE_ACCOUNT_JSON. To configure a service account for upload to the Play Store, follow these steps in the Android documentation.

5.4.7. HelloSharedLib Sample

Clone HelloSharedLib on your system and run the mvn -Pandroid gluonfx:sharedlib goal. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:sharedlib (default-cli) @ hellosharedlib ---
[INFO] ==================== SHARED LIBRARY TASK ====================
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hello.hellosharedlib' (shared library)...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                       (3.2s @ 0.20GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB]  Garbage collector: Serial GC
[INFO] [SUB] [2/7] Performing analysis...  [**********]                                                 (24.8s @ 1.48GB)
[INFO] [SUB]    6,022 (86.86%) of  6,933 classes reachable
[INFO] [SUB]    7,636 (53.38%) of 14,306 fields reachable
[INFO] [SUB]   27,263 (55.45%) of 49,169 methods reachable
[INFO] [SUB]      379 classes,   105 fields, and 1,035 methods registered for reflection
[INFO] [SUB]       83 classes,    75 fields, and    97 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (1.8s @ 1.90GB)
[INFO] [SUB] [4/7] Parsing methods...      [*]                                                           (1.9s @ 2.54GB)
[INFO] [SUB] [5/7] Inlining methods...     [****]                                                        (1.9s @ 2.29GB)
[INFO] [SUB] [6/7] Compiling methods...    [****]                                                       (16.8s @ 2.65GB)
[INFO] [SUB]
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]                         4.1s (7.0% of total time) in 25 GCs | Peak RSS: 5.11GB | CPU load: 8.77
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] Produced artifacts:
[INFO] [SUB]  /gluon-samples/HelloSharedLib/target/gluonfx/aarch64-android/gvm/HelloSharedLib/graal_isolate.h (header)
[INFO] [SUB]  /gluon-samples/HelloSharedLib/target/gluonfx/aarch64-android/gvm/HelloSharedLib/hello.hellosharedlib.h (header)
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hello.hellosharedlib' in 57.7s.

And as a result, HelloSharedLib.so is created and can be found under target/gluonfx/aarch64-android/.

To test the shared library on a regular Android project that includes already C++ code, add the headers:

  • target/gluonfx/aarch64-android/gvm/HelloSharedLib/graal_isolate.h

  • target/gluonfx/aarch64-android/gvm/HelloSharedLib/hello.hellosharedlib.h

and the library:

  • target/gluonfx/aarch64-android/HelloSharedLib.dylib

to the project.

In the Cpp file with the JNI implementations, import the header: --- #include <hello.hellosharedlib.h> ---

and then add the calls to the shared methods, like:


static jboolean initialized; static graal_isolatethread_t *thread;

extern "C" JNIEXPORT jdouble JNICALL Java_com_gluonhq_hellosharedandroidworld_MainActivity_sum(JNIEnv *env, jobject activity, jdouble a, jdouble b) { if (!initialized) { if (graal_create_isolate(NULL, NULL, &thread) != 0) { fprintf(stderr, "graal_create_isolate error\n"); return -1; } initialized = true; } return sharedSum(thread, a, b); } ---

assuming the MainActivity class loads the native library and calls the native method:


package com.gluonhq.hellosharedandroidworld;

public class MainActivity extends AppCompatActivity {

static {
    System.loadLibrary("HelloSharedLib");
}
 @Override
 protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     ...
     Log.v(TAG, "Sum: " + sum(1, 3));
}
    private native double sum(double a, double b);
}
---

Build, making sure the shared library is added to the APK, and deploy to your device and test.

The output of the given call should look like:

2022-07-20 21:06:32.348 22110-22110/com.gluonhq.hellosharedandroidworld V/MainActivity: Sum: 3.0
2022-07-20 21:06:32.348 22110-22110/com.gluonhq.hellosharedandroidworld V/MainActivity: Difference: -1.0
2022-07-20 21:06:32.348 22110-22110/com.gluonhq.hellosharedandroidworld V/MainActivity: Text: Hello from Java

5.5. iOS

5.5.1. Pre-requisites

  • Pre-requisites of Mac OS Platform

  • Additional packages (more information on this later)

Currently, iOS packages can be built only on Mac OS X. Alternatively you can use a GitHub Actions workflow.

iOS applications can be locally deployed to a physical device (iPhone and iPad), or distributed via App Store.

While developing, it might also help building native images that can be deployed to the iOS Simulator.

In addition to GraalVM for Mac OS, the following packages are required and can be installed using Homebrew:

  • brew install libusbmuxd

  • brew install libimobiledevice

5.5.2. iOS Development

If you want to test and deploy your app on a local iOS device, you’ll need to enroll in the Apple Developer Program. If you’re not ready for that or just want to test our your personal device, you can also use a "free provisioning profile".

Either way, first of all you will need a unique Apple ID.

Creating an Apple ID

In case you don’t have it yet, create an Apple ID.

Create Apple ID

You will need a valid email account, and you will receive and email to validate and activate the Apple ID.

In case you are going to use free provisioning, this Apple ID must not be connected to the Apple Developer Program.

Once you have your Apple ID verified and activated, open Xcode, go to Preferences → Accounts. Press the + button at the bottom left to add an account, select `Apple ID, and include the email address used to create the Apple ID.

Create account
Xcode project

The first time you start deploying to an iOS device, you need to follow these steps, so Xcode configures your device properly for future iOS deployment.

Note: If you are using free provisioning, you will have to follow this procedure every time you create a new project.

Create an empty Xcode project

Open Xcode and go to File → New → Project…​, and select a simple template for iOS, like Single View App.

Single View App
Name and bundle

Provide a name and a bundle ID to the project.

Note: if you are using free provisioning, the bundle ID must match the one defined in your Java project (or the other way around).

Project data

Press next, provide a location and create the project.

Signing the project

Once the project is created, it will be opened and you will have access to the General tab with the identity and signing information.

At this point you should plug your iOS device, and select it from the top drop-down list of build schemes that list your iOS device and iOS simulators.

Project general

Make sure you have selected Automatically manage signing, and select a valid Team from the drop-down list.

If you get the error like: The app ID "hellofx.HelloFX" cannot be registered to your develoment team, this means that the proposed app ID is already in use or has already been registered by the Apple servers, and cannot longer be used. The only solution is to change the app ID until you get a valid one:

Valid app ID

Note that the provisioning profile generated by Xcode for that app ID can be found in your ~/Library/MobileDevice/Provisioning Profiles folder.

For free provisioning, this profile will expire after one week.

Deploying to your device

Once everything is in place (there are no visible errors in the General screen), press the Play button that will build and run the current scheme.

Note: Using free provisioning it is possible that the run fails with a message like: Could not launch "HelloFX", that prompts you to verify the Developer App certificate is trusted on your device, as by default the certificate is untrusted. Go to your iPhone, Settings → General → Device Management, and select the certificate to trust it. Then go back to Xcode and run again. This time, the app will launch and you will get a white screen.

Press stop to finish and quit Xcode.

Create your application with GluonFX plugin

These are the required steps to build and deploy your iOS application from your Java project.

Build your application

Back to your Java project, run mvn -Pios gluonfx:build to build and link your native binary.

Package your application

Then you need to run mvn -Pios gluonfx:package to create both .app and .ipa bundles. Signing is required for both.

In this process, default resources are generated under target/gluonfx/arm64-ios/gensrc/ios, including the Default-Info.plist file and the assets folder with a default iconset.

If needed, you can modify these default values and the iconset, by copying them to src/ios, doing the required changes, and running mvn gluonfx:package again.

Note that if you are using free provisioning or an explicit provisioning profile, you need to set the exact same bundle ID as the one in the profile (note it is case sensitive).

If you are using a wildcard, make sure the domain (if any) matches.

This can be done by setting the <appIdentifier/> in the configuration of the GluonFX plugin. Alternatively, it can be done as well by modifying the default plist.

Note: the bundle identifier key doesn’t need to match your main class.

Checking the logs under target/gluonfx/arm64-ios/gvm/logs or using <verbose>true</verbose> in the GluonFX plugin can be of help to trace which provisioning profile is used, and to check that a valid one was found:

[FINE] Mobile provision asked with bundleId = hello.fx.* (initial bundleId: hello.fx.HelloFX)
[FINE] Checking mobile provision Xcode iOS Wildcard App ID
[FINE] AllInOne matches SigningIdentity{name='Apple Development: ************', sha1='05F2BD84A3************B234CEBC'}
[FINE] Got provisioning profile: AllInOne
Install your application

If the package task finishes successfully, you can deploy the .app bundle to a connected device, running mvn -Pios gluonfx:install.

Note: If you are using free provisioning, this will deploy your Java app replacing the Xcode app.

Run your application

Once the install task has finished successfully, and with your iOS device plugged in, run: mvn -Pios gluonfx:nativerun.

You should get your app running on your iOS device and the output of the process will be displayed to the terminal.

5.5.3. iOS Distribution

To distribute your app using the App Store, you (as individual or as a company) must enroll in the Apple Developer Program.

For iOS distribution, you need to generate:

  • A valid signing identity

  • A valid provisioning profile

The following steps describe how these can be generated.

iOS Distribution Certificate

Go to the Developer portal, and access Certificates, Identifiers & Profiles. Select Certificates from the left, and press +, to create a Development certificate (for testing). Later on you will need a Distribution certificate for distribution through the App Store.

New Certificate

To generate a certificate, you need a Certificate Signing Request (CSR) file from your Mac.

Certificate Signing Request

According to Apple Help, these are the steps to generate the CSR:

  • Launch the Keychain Access application, located in /Applications/Utilities.

  • Choose Keychain Access → Certificate Assistant → Request a Certificate from a Certificate Authority.

  • Enter an email address in the User Email Address field.

  • Enter a name for the key.

  • Leave the CA Email Address field empty.

  • Choose Saved to disk, and click Continue.

  • Save the CSR file.

Upload CSR

Return to the Developer portal and upload the CSR:

Upload CSR
Download and install the certificate

Once the CSR is uploaded, a certificate is generated. Download this certificate to your Mac.

Download Certificate

Then double-click the .cer file to install in Keychain Access.

Identifiers

Go to the Developer portal, and access Certificates, Identifiers & Profiles. Select Identifiers from the left, and press +, to create a new app identifier.

You can enable app services when you create the App ID or modify these settings later on.

New App Identifier

Provide a name and make sure you use the exact Bundle ID from your app, the one listed in your Default-Info.plist file under the CFBundleIdentifier key.

App ID

Alternatively, you can use a wildcard that will let you use a single App ID to match multiple apps. It is recommended to use your domain name, to filter out other apps.

Wildcard

Select any of the required services (if you are using an explicit Bundle ID), and click continue to create the App ID.

Devices

To create a provisioning profile for development, the devices that are allowed to run the application have to be identified.

To add your testing devices, you need to provide their UDID.It can be found from Xcode → Window → Devices and Simulators. Plug your device, select it from Connected devices, and then select its identifier and copy it.

Device identifier

Go to the Developer portal, and access Certificates, Identifiers & Profiles. Select Devices from the left, and press +, to add a new device.

Add a name and paste the device’s identifier.

Add Device
Profiles

Go to the Developer portal, and access Certificates, Identifiers & Profiles. Select Profiles from the left, and press +, to add a new provisioning profile.

To add a Development profile (later on you will need a Distribution one), select iOS App Development, and press continue.

iOS App Development

From the drop down list, select a valid App ID:

  • If you plan to use services such as Game Center, In-App Purchase, and Push Notifications, or want a Bundle ID unique to a single app, use an explicit App ID.

  • If you want to create one provisioning profile for multiple apps or don’t need a specific Bundle ID, select a wildcard App ID.

iOS App ID

Press continue and select one or more certificates that should be included in this provisioning profile.

Press continue and select one or more devices that should be include in this provisioning profile.

Press continue and finally, give a name to the provisioning profile, it will be used to identify the profile in the portal. Then press Generate.

Provisioning profile

When finished, download it and install it by double clicking on the file.

Build your application

You can run mvn gluonfx:build gluonfx:package to build and package your native application.

To make sure you’re using the correct signing identify and provisioning profile, you can set these using the releaseConfiguration settings:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
...
    <configuration>
...
        <releaseConfiguration>
            <bundleVersion>1</bundleVersion>
            <bundleShortVersion>1.0</bundleShortVersion>
            <providedSigningIdentity>iPhone Distribution: Gluon Software BVBA (**********)</providedSigningIdentity>
            <providedProvisioningProfile>my-provisioning-profile</providedProvisioningProfile>
        </releaseConfiguration>
...
   </configuration>
</plugin>
bundleVersion is the unique build number of your app. You need to increase it for every upload to TestFlight.
Note that you can omit providedSigningIdentity and providedProvisioningProfile when running in a Github Actions workflow, since there will be only 1 valid identity, that one will be used be default.
Override default iOS settings

Run at least once the link goal for iOS (mvn gluonfx:link) and the Default-Info.plist file will be created at target/gluonfx/arm64-ios/gensrc/ios/Default-Info.plist.

If you need to set specific iOS settings, copy that file into:

src/ios/Default-Info.plist

and perform the necessary changes. Running again the link goal will now use this plist file.

Override the default icon set

By default, during the link goal an icon set is generated in target/gluonfx/arm64-ios/gensrc/ios/assets/Assets.xcassets/AppIcon.appiconset. If you’re distributing your app, you most certainly will want to use a custom icon.

Please refer to the Apple docs for more information.

However, if you would like to override the AppIcon, there are nice online generators as well (e.g. https://appicon.co/)

To use the generated icon follow these steps:

  • copy target/gluonfx/arm64-ios/gensrc/ios/assets/Assets.xcassets to src/ios/assets

  • then remove src/ios/assets/Assets.xcassets/AppIcon.appiconset

  • then copy the generated AppIcon.appiconset to src/ios/assets/Assets.xcassets

The next time you run the link goal, this icon set will be used.

Override the default storyboards

By default, during the link goal, a pair of blank storyboards are generated in target/gluonfx/arm64-ios/gensrc/ios/assets/Base.lproj. Once the app is launched, these will be used briefly before the JavaFX stage is shown, preventing a black screen.

If you want to customize the storyboards, copy them to:

src/ios/assets/Base.lproj

and perform the necessary changes. Running again the link goal will now use these storyboards.

Adding capabilities to your app

If you need to add capabilities to your app, you will need to add the required entitlements to sign it.

For that purpose, you can add the following file:

src/ios/Entitlements.plist

and include the necessary entitlements.

For instance, if your app includes the App Group capability, the file should contain something like:

<?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>com.apple.security.application-groups</key>
    <array>
        <string>${your.team.id}.${your.bundle.id}</string>
    </array>
</dict>
</plist>

Note that you should replace ${your.team.id} with your Apple Developer account’s Team ID and ${your.bundle.id} with the App ID of the app.

Running the link goal will include the changes.

5.5.4. HelloFX Sample

This requires an iOS device that has to be plugged in at the run phase. A valid provisioning profile is required as well, either by enrolling to the Apple Developer program or by using free provisioning. See iOS Deployment.

Clone HelloFX On a Mac OS X system. Use the ios profile and run mvn -Pios gluonfx:compile. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:compile (default-cli) @ hellofx ---
[INFO] ==================== COMPILE TASK ====================
[INFO] We will now compile your code for arm64-apple-ios. This may take some time.
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hellofx.hellofx'...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                       (5.3s @ 0.12GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB] [2/7] Performing analysis...  [***********]                                                (74.3s @ 2.72GB)
[INFO] [SUB]   11,110 (89.16%) of 12,461 classes reachable
[INFO] [SUB]   19,894 (73.23%) of 27,166 fields reachable
[INFO] [SUB]   54,012 (61.33%) of 88,071 methods reachable
[INFO] [SUB]      654 classes,   109 fields, and 1,318 methods registered for reflection
[INFO] [SUB]      114 classes,   105 fields, and   191 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (3.6s @ 3.51GB)
[INFO] [SUB] [4/7] Parsing methods...      [***]                                                         (7.1s @ 1.11GB)
[INFO] [SUB] [5/7] Inlining methods...     [*****]                                                       (7.3s @ 2.73GB)
[INFO] [SUB] [6/7] Compiling methods...    [*******]                                                    (56.2s @ 2.06GB)
[INFO] [SUB] [7/7] Creating image...
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]          17.9s (10.5% of total time) in 36 GCs | Peak RSS: 6.05GB | CPU load: 5.36
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hellofx.hellofx' in 2m 49s.

And as a result, hellofx.hellofx.o is created and can be found under target/gluonfx/arm64-ios/gvm/tmp/SVM-*/hellofx.hellofx.o.

Run mvn -Pios gluonfx:link to produce the native image. As a result, the binary target/gluonfx/arm64-ios/hellofx.app/hellofx is created.

Run mvn -Pios gluonfx:package to sign and produce the application bundles. As a result, target/gluonfx/arm64-ios/hellofx.app and target/gluonfx/arm64-ios/hellofx.ipa are created.

HelloFX iOS packages

While the .ipa bundle could be submitted to the App Store, the .app bundle can be deployed to a plugged iOS device with mvn -Pios gluonfx:install.

And then the app can be launched with mvn -Pios gluonfx:nativerun.

HelloFX iOS deployed

5.5.5. iOS builds using Github Actions

The following sections show how to use Github Actions to build and release your app automatically using Github infrastructure. You don’t need a Mac, except for a one-time config step to setup the distribution certificate for the App Store. Using this Github workflow, you can develop your JavaFX application anywhere you like and the Github workflow will make your application available for testing in TestFlight on push.

Github Workflow File

Using this Github workflow, you can checkout, build, and upload the native binary to TestFlight. The steps are described in code comments and more details information can be found in the next sections.

jobs:
  build:
    runs-on: macos-latest
    steps:
      # Checkout your code
      - uses: actions/checkout@v2

      # Make sure the latest GraalVM is installed.
      # after this step env.JAVA_HOME and env.GRAALVM_HOME will point to the GraalVM location
      - name: Setup GraalVM built by Gluon
        uses: gluonhq/setup-graalvm@master
        # set GITHUB_TOKEN to avoid exceeding GitHub's API rate limit
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      # Setup the signing identify. See the section 'Configuring the signing identity'
      - uses: Apple-Actions/import-codesign-certs@v1
        with:
          p12-file-base64: ${{ secrets.GLUON_IOS_CERTIFICATES_FILE_BASE64 }}
          p12-password: ${{ secrets.GLUON_IOS_CERTIFICATES_PASSWORD }}

      # Download the appropriate provisining profile using Apple's Appstore Connect API.  See the section 'Using the Appstore Connect API' below
      - uses: Apple-Actions/download-provisioning-profiles@v1
        with:
          bundle-id: com.gluonhq.hello.HelloGluonApp
          issuer-id: ${{ secrets.GLUON_IOS_APPSTORE_ISSUER_ID }}
          api-key-id: ${{ secrets.GLUON_IOS_APPSTORE_KEY_ID }}
          api-private-key: ${{ secrets.GLUON_IOS_APPSTORE_PRIVATE_KEY }}

      # Install the Gluon License (optional)
      # Using this step requires you to set a GLUON_LICENSE secret in the secret configuration of your repo. Have a look at https://gluonhq.com/products/mobile/buy for more information about obtaining a license.
      - name: Gluon License
        uses: gluonhq/gluon-build-license@v1
        with:
          gluon-license: ${{ secrets.GLUON_LICENSE }}

      # Build your project using Maven
      # The ios profile is used, which means a native build will be created for the gluonfx target ios.
      - name: Gluon Build
        run: mvn -Pios gluonfx:build gluonfx:package

      # Upload the build .ipa file to TestFlight using the Appstore Connect API.
      - uses: Apple-Actions/upload-testflight-build@master
        with:
          app-path: target/gluonfx/arm64-ios/HelloGluon.ipa
          issuer-id: ${{ secrets.GLUON_IOS_APPSTORE_ISSUER_ID }}
          api-key-id: ${{ secrets.GLUON_IOS_APPSTORE_KEY_ID }}
          api-private-key: ${{ secrets.GLUON_IOS_APPSTORE_PRIVATE_KEY }}

You can see this workflow in action in the Hello Gluon CI Sample.

Changes to your build

Since this workflow uploads the build to TestFlight, it needs a unique build number each time. If you have overriden the Default-Info.plist settings you must make sure to increment the CFBundleVersion. If you didn’t override the Default-Info.plist, you can use this gluonfx configuration:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
...
    <configuration>
...
        <releaseConfiguration>
            <bundleVersion>${env.GITHUB_RUN_NUMBER}</bundleVersion>
            ...
        </releaseConfiguration>
...
   </configuration>
</plugin>

Using env.GITHUB_RUN_NUMBER, Github Actions will set the bundleVersion to an incremental build number.

Configuring the signing identity

The workflow step Apple-Actions/import-codesign-certs above uses 2 configurations from your repo’s secrets configuration:

  • GLUON_IOS_CERTIFICATES_FILE_BASE64

  • GLUON_IOS_CERTIFICATES_PASSWORD

Please refer to the Github Actions docs for more information on how to set them.

To generate the values for these secrets, you’ll need a Mac for this one time configuration and follow the steps described in the iOS Distribution Certificate section.

Once you imported the certificate in Keychain Access, follow these steps:

  • Make sure to expand the certificate so you can also see the private key that was used to sign it.

  • Select both the certificate and private key and choose File→Export items

Keychain Export
  • Export it to a .p12 file and choose a password. Set this password as GLUON_IOS_CERTIFICATES_PASSWORD

  • Base64 encode the .p12 file. Set this value as GLUON_IOS_CERTIFICATES_FILE_BASE64

Next to the signing identify, you will also need to create a provisining profile that can be used for distribution. However, it does not have to be downloaded. A workflow step will download it using the Appstore Connect API. (see next section)

Using the Appstore Connect API

The above workflow uses the Appstore Connect API to automate these 2 steps:

  • Download the provisioning profile

  • Upload the .ipa file to TestFlight, from where it can be tested and moved to production.

To generate an Appstore Connect API key, follow these steps as described by Apple.

Following that, you can set these repo secrets:

  • GLUON_IOS_APPSTORE_ISSUER_ID: see image arrow 1

  • GLUON_IOS_APPSTORE_KEY_ID: see image arrow 2

  • GLUON_IOS_APPSTORE_PRIVATE_KEY: the text contents of the file you downloaded while creating the API key.

Appstore Connect API

5.5.6. iOS Simulator

Since: 1.0.12

Using the iOS simulator prevents the need of having a physical device for testing an iOS application. It is also a way to create screenshots for the multiple formats and resolutions that the App store requires for distribution.

While the iOS app that is deployed to an iPhone or distributed via the Apple Store is exactly the same as the one that runs locally on the simulator, there is an important difference: the former has AArch64 architecture, while the latter has x86_64.

This means that the native image created with target ios can’t run on the iOS simulator, and a new native image has to be created, with target ios-sim.

Pre-requisites

In this case, an Apple ID is not needed.

Configuration

To target the iOS Simulator, <target>ios-sim</target> needs to be added to the GluonFX plugin configuration:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        <target>ios-sim</target>
        <mainClass>${mainClassName}</mainClass>
    </configuration>
</plugin>

Alternatively, a Maven profile can be used:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        <target>${gluonfx.target}</target>
        <mainClass>${mainClassName}</mainClass>
    </configuration>
</plugin>
<profiles>
    <profile>
        <id>ios-sim</id>
        <properties>
            <gluonfx.target>ios-sim</gluonfx.target>
        </properties>
    </profile>
</profiles>

To target a given simulator device, use simulatorDevice. For instance:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        <target>ios-sim</target>
        <mainClass>${mainClassName}</mainClass>
        <releaseConfiguration>
            <simulatorDevice>iPhone 13 Pro Max</simulatorDevice>
        </releaseConfiguration>
    </configuration>
</plugin>

If not set, iPhone 13 is used by default.

HelloFX sample

Clone HelloFX On a Mac OS X system. Use the ios-sim profile and run mvn -Pios-sim gluonfx:compile. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:compile (default-cli) @ hellofx ---
[INFO] ==================== COMPILE TASK ====================
[INFO] We will now compile your code for x86_64-apple-darwin. This may take some time.
[INFO] [SUB] Warning: Ignoring server-mode native-image argument --no-server.
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hellofx.hellofx'...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                       (6.0s @ 0.14GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB] [2/7] Performing analysis...  [***********]                                                (77.5s @ 4.43GB)
[INFO] [SUB]   11,220 (89.14%) of 12,587 classes reachable
[INFO] [SUB]   20,083 (73.02%) of 27,504 fields reachable
[INFO] [SUB]   54,373 (61.35%) of 88,632 methods reachable
[INFO] [SUB]      651 classes,   109 fields, and 1,316 methods registered for reflection
[INFO] [SUB]      115 classes,   105 fields, and   192 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (3.9s @ 1.84GB)
[INFO] [SUB] [4/7] Parsing methods...      [**]                                                          (3.4s @ 3.12GB)
[INFO] [SUB] [5/7] Inlining methods...     [*****]                                                      (11.6s @ 3.72GB)
[INFO] [SUB] [6/7] Compiling methods...    [******]                                                     (40.7s @ 2.61GB)
[INFO] [SUB] [7/7] Creating image...
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]                         15.3s (9.7% of total time) in 33 GCs | Peak RSS: 6.41GB | CPU load: 5.55
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hellofx.hellofx' in 2m 36s.

And as a result, hellofx.hellofx.o is created and can be found under target/gluonfx/x86_64-ios/gvm/tmp/SVM-*/hellofx.hellofx.o.

Run mvn -Pios-sim gluonfx:link to produce the native image. As a result, the binary target/gluonfx/x86_64-ios/hellofx.app/hellofx is created.

Run mvn -Pios-sim gluonfx:package to produce the application bundles. As a result, target/gluonfx/x86_64-ios/hellofx.app is created.

Run mvn -Pios-sim gluonfx:install to install that app into the iOS simulator. A valid simulator device is required. If simulatorDevice is not specified, iPhone 13 will be booted as simulator device.

Finally, the app can be launched with mvn -Pios-sim gluonfx:nativerun.

HelloFX iOS simulator

5.5.7. HelloSharedLib Sample

Clone HelloFX On a Mac OS X system. Use the ios profile and run mvn -Pios gluonfx:compile. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:compile (default-cli) @ hellofx ---
[INFO] ==================== COMPILE TASK ====================
[INFO] We will now compile your code for arm64-apple-ios. This may take some time.
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hellofx.hellofx'...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                       (5.3s @ 0.12GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB] [2/7] Performing analysis...  [***********]                                                (74.3s @ 2.72GB)
[INFO] [SUB]   11,110 (89.16%) of 12,461 classes reachable
[INFO] [SUB]   19,894 (73.23%) of 27,166 fields reachable
[INFO] [SUB]   54,012 (61.33%) of 88,071 methods reachable
[INFO] [SUB]      654 classes,   109 fields, and 1,318 methods registered for reflection
[INFO] [SUB]      114 classes,   105 fields, and   191 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (3.6s @ 3.51GB)
[INFO] [SUB] [4/7] Parsing methods...      [***]                                                         (7.1s @ 1.11GB)
[INFO] [SUB] [5/7] Inlining methods...     [*****]                                                       (7.3s @ 2.73GB)
[INFO] [SUB] [6/7] Compiling methods...    [*******]                                                    (56.2s @ 2.06GB)
[INFO] [SUB] [7/7] Creating image...
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]          17.9s (10.5% of total time) in 36 GCs | Peak RSS: 6.05GB | CPU load: 5.36
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hellofx.hellofx' in 2m 49s.

And as a result, hellofx.hellofx.o is created and can be found under target/gluonfx/arm64-ios/gvm/tmp/SVM-*/hellofx.hellofx.o.

Run mvn -Pios gluonfx:link to produce the native image. As a result, the binary target/gluonfx/arm64-ios/hellofx.app/hellofx is created.

Run mvn -Pios gluonfx:package to sign and produce the application bundles. As a result, target/gluonfx/arm64-ios/hellofx.app and target/gluonfx/arm64-ios/hellofx.ipa are created.

HelloFX iOS packages

While the .ipa bundle could be submitted to the App Store, the .app bundle can be deployed to a plugged iOS device with mvn -Pios gluonfx:install.

And then the app can be launched with mvn -Pios gluonfx:nativerun.

HelloFX iOS deployed

Clone HelloSharedLib on a Mac OS X system. Use the ios profile and run mvn -Pios gluonfx:sharedlib. It produces the following output:

[INFO] --- gluonfx-maven-plugin:1.0.22:sharedlib (default-cli) @ hellosharedlib ---
[INFO] ==================== SHARED LIBRARY TASK ====================
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] GraalVM Native Image: Generating 'hello.hellosharedlib' (shared library)...
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] [1/7] Initializing...                                                                       (1.5s @ 0.17GB)
[INFO] [SUB]  Version info: 'GraalVM 22.1.0.1 Java 17 CE'
[INFO] [SUB]  Garbage collector: Serial GC
[INFO] [SUB] [2/7] Performing analysis...  [**************]                                             (12.5s @ 1.96GB)
[INFO] [SUB]    6,015 (87.17%) of  6,900 classes reachable
[INFO] [SUB]    7,627 (53.40%) of 14,284 fields reachable
[INFO] [SUB]   27,235 (55.63%) of 48,960 methods reachable
[INFO] [SUB]      384 classes,   107 fields, and 1,038 methods registered for reflection
[INFO] [SUB]       83 classes,    76 fields, and    97 methods registered for JNI access
[INFO] [SUB] [3/7] Building universe...                                                                  (1.1s @ 2.43GB)
[INFO] [SUB] [4/7] Parsing methods...      [*]                                                           (1.0s @ 3.08GB)
[INFO] [SUB] [5/7] Inlining methods...     [****]                                                        (2.6s @ 2.63GB)
[INFO] [SUB] [6/7] Compiling methods...    [***]                                                         (8.1s @ 2.07GB)
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB]                         2.4s (7.8% of total time) in 23 GCs | Peak RSS: 4.56GB | CPU load: 5.98
[INFO] [SUB] -----------------------------------------------------------------------------------------------------------
[INFO] [SUB] Produced artifacts:
[INFO] [SUB]  /gluon-samples/HelloSharedLib/target/gluonfx/arm64-ios/gvm/HelloSharedLib/graal_isolate.h (header)
[INFO] [SUB]  /gluon-samples/HelloSharedLib/target/gluonfx/arm64-ios/gvm/HelloSharedLib/hello.hellosharedlib_dynamic.h (header)
[INFO] [SUB] ===========================================================================================================
[INFO] [SUB] Finished generating 'hello.hellosharedlib' in 30.6s.

And as a result, HelloSharedLib.dylib is created and can be found under target/gluonfx/arm64-ios/.

To test the shared library on a regular iOS project, add the headers:

  • target/gluonfx/arm64-ios/gvm/HelloSharedLib/graal_isolate.h

  • target/gluonfx/arm64-ios/gvm/HelloSharedLib/hello.hellosharedlib.h

and the library:

  • target/gluonfx/arm64-ios/HelloSharedLib.dylib

to the iOS project.

In graal_isolate.h, change: --- #include <graal_isolate.h> ---

into


#include "graal_isolate.h" ---

Add the required import to the project, for instance to ViewController.h:


#import "hello.hellosharedlib.h" ---

Add some code that makes use of the shared methods to your controller, like:


-(void) runFromSharedLib:(UIButton*)sender { graal_isolatethread_t *thread = NULL; if (graal_create_isolate(NULL, NULL, &thread) != 0) { fprintf(stderr, "graal_create_isolate error\n"); return; }

double resultSum = sharedSum(thread, 1.0, 2.0);
double resultDiff = sharedDiff(thread, 1.0, 2.0);
char* resultText = sharedText(thread);
NSLog(@"Shared sum: %.1f", resultSum);
NSLog(@"Shared diff: %.1f", resultDiff);
NSLog(@"Shared text: %s", resultText);
  if (graal_detach_thread(thread) != 0) {
       fprintf(stderr, "graal_detach_thread error\n");
       return;
  }
}
---

Before building the project, disable bitcode, link the binary with the library, and copy it to frameworks.

Build, and deploy to your device and test.

The output of the given call should look like:

2022-07-20 19:59:18.762690+0200 HelloSharedLib[51489:9848288] Shared sum: 3.0
2022-07-20 19:59:18.762903+0200 HelloSharedLib[51489:9848288] Shared diff: -1.0
2022-07-20 19:59:18.763037+0200 HelloSharedLib[51489:9848288] Shared text: Hello from Java

5.6. JavaFX on Embedded

Gluon applications can run on JVM for ARM devices (32/64 bits) and, additionally, native images can be created for AArch64.

The following section describes the necessary steps to create and run JavaFX applications on a Raspberry Pi (both 32 and 64 bits), using a JDK for ARM, while the next section describes the steps to create a native image for AArch64.

Of course, JavaFX is capable of running on a large number of other hardware and operating systems. Please contact us if you want more information about a specific configuration.

5.6.1. Setting up the Raspberry Pi 4

There are plenty of configurations with different hardware and operating systems that require different versions of binaries, linked to different libraries.

The distribution of JavaFX 22-ea for embedded is tested on a Raspberry Pi 4, using the Raspberry Pi OS distribution.

The Raspberry Pi site contains clear documentation about the product itself. Installing an operating system for the Raspberry Pi is described at https://www.raspberrypi.org/documentation/installation/installing-images/README.md.

The following documentation has been tested to work with Raspberry Pi OS (both 32-bit and 64-bit) with desktop and recommended software, with images that can be downloaded from https://www.raspberrypi.com/software/operating-systems/. However, it is highly recommended to use the Raspberry Pi Imager (available for Windows, Mac OS and Linux), to download and install the image to the SDCard.

Once the image is installed, the first boot will be on X11, and a configuration dialog will show up. Follow the instructions to configure your language, keyboard settings and WiFi. Apply the changes and reboot.

Once you boot again, from the top menu, select Preferences → Raspberry Pi Configuration

  • From the System tab, select Boot to CLI. Note that the hostname is set to raspberrypi (change it if needed).

  • From Interfaces tab, enable SSH.

  • From Performance tab, it is recommended to set 512 MB for GPU Memory, depending of course on how graphics-intensive versus how cpu-intensive your application is.

Click OK and reboot.

Once the Pi is back online, it is convenient to check the SSH access from your machine:

ssh pi@raspberrrypi.local

If you encounter any issues, check these instructions.

5.6.2. Java

The Raspberry Pi OS might contain an up-to-date JDK 17 distribution for ARM. You can verify that by running java -version.

If you run on a distribution without Java installed (like Raspberry Pi OS Lite), you can easily install it with:

pi@raspberrypi:~ $ sudo apt update
$ sudo apt-get install openjdk-17-jdk
$ java -version
openjdk version "17.0.4" 2022-07-19
OpenJDK Runtime Environment (build 17.0.4+8-Debian-1deb11u1)
OpenJDK 64-Bit Server VM (build 17.0.4+8-Debian-1deb11u1, mixed mode, sharing)

You can still use JDK 11, but you will need to use a JavaFX version lower than 20. If needed, install it with:

pi@raspberrypi:~ $ sudo apt-get install openjdk-11-jdk

5.6.3. JavaFX

A reference implementation of JavaFX 22-ea for embedded devices, with support for DRM, can be downloaded for the Raspberry Pi 4 from https://gluonhq.com/products/javafx/#ea.

You can choose the JavaFX SDK for 32 or 64 bits, based on the OS installed on your device.

This SDK should work on other similar systems. If you have an embedded system that you want to be supported or if you need specific builds, contact us.

In order to support hardware-accelerated rendering, JavaFX relies on a number of low-level drivers and libraries that are not always installed by default on all embedded systems. That is the case of the Raspberry Pi OS Lite distribution, for instance, and you can install additional required libraries with:

pi@raspberrypi:~ $ sudo apt install libegl-mesa0 libegl1 libgbm1 libgles2 libpango-1.0-0 libpangoft2-1.0-0
Install JavaFX for 32 bits

Browse for Linux OS, arm32 architecture and SDK type, download the SDK to your machine and then copy it to the Pi, like:

scp ~/Downloads/openjfx-21-ea+11.1_linux-arm32_bin-sdk.zip pi@raspberrypi.local:/home/pi/Downloads

Now back to your Pi (via SSH or directly), move the SDK to /opt and unzip it:

pi@raspberrypi:~ $ sudo mv ~/Downloads/openjfx-21-ea+11.1_linux-arm32_bin-sdk.zip /opt
$ cd /opt
$ sudo unzip openjfx-21-ea+11.1_linux-arm32_bin-sdk.zip
$ sudo rm openjfx-21-ea+11.1_linux-arm32_bin-sdk.zip

Note that you should find the SDK under the folder /opt/javafx-sdk-21.

Install JavaFX for 64 bits

Browse for Linux OS, aarch64 architecture and Monocle SDK type, download the SDK to your machine and then copy it to the Pi, like:

scp ~/Downloads/openjfx-21-ea+11.1_monocle-linux-aarch64_bin-sdk.zip  pi@raspberrypi.local:/home/pi/Downloads

Now back to your Pi (via SSH or directly), move the SDK to /opt and unzip it:

pi@raspberrypi:~ $ sudo mv ~/Downloads/openjfx-21-ea+11.1_monocle-linux-aarch64_bin-sdk.zip /opt
$ cd /opt
$ sudo unzip openjfx-21-ea+11.1_monocle-linux-aarch64_bin-sdk.zip
$ sudo rm openjfx-21-ea+11.1_monocle-linux-aarch64_bin-sdk.zip

Note that you should find the SDK under the folder /opt/javafx-sdk-21.

DRM library

JavaFX requires the DRM library to work without a windows manager.

This library is already bundled within the JavaFX SDK (for the ea versions), and it can be used without limitations for non-commercial applications. After installing JavaFX, check that you have it:

$ ls -l /opt/javafx-sdk-21/lib/libgluon_drm*
-rwxr-xr-x 1 root root 34208 Dec 12 20:42 javafx-sdk-21/lib/libgluon_drm-1.1.7.so
-rwxr-xr-x 1 root root 67448 Dec 12 20:42 javafx-sdk-21/lib/libgluon_drm_debug-1.1.7.so

In any case, you can download it from here:

And copy the file libgluon_drm-1.1.7.so to any location, but it can be convenient to have it under /opt/javafx-sdk-21/lib.

JavaFX support for DRM is a commercial extension from Gluon. See license file.

You can enable it by setting the environment variable ENABLE_GLUON_COMMERCIAL_EXTENSIONS, either if your application is non-commercial or if you obtained a valid license from Gluon.

To enable it on your current session, run:

pi@raspberrypi:~ $ export ENABLE_GLUON_COMMERCIAL_EXTENSIONS=true

And if you want to export it permanently, you can add the following line to your .bashrc file:

ENABLE_GLUON_COMMERCIAL_EXTENSIONS=true
Testing JavaFX
JavaFX on embedded works with X11 as well, but first we want to demonstrate it works using the DRM driver to manage the framebuffer, without X.

Create a JavaFX sample, or clone the OpenJFX samples.

Compile:

pi@raspberrypi:~ $ cd ~/Downloads/samples/CommandLine/Modular/CLI/hellofx
$ javac --module-path /opt/javafx-sdk-21/lib --add-modules=javafx.controls src/hellofx/HelloFX.java -d dist

And finally run:

pi@raspberrypi:~ $ sudo -E java -Dmonocle.platform=EGL -Dembedded=monocle -Dglass.platform=Monocle -Duse.egl=true -Degl.displayid=/dev/dri/card1 -Dmonocle.egl.lib=/opt/javafx-sdk-21/lib/libgluon_drm-1.1.7.so --module-path /opt/javafx-sdk-21/lib --add-modules javafx.controls -cp dist/. hellofx.HelloFX

You should see on your display something like:

HelloFX app

while your SSH session shows:

[GluonDRM] use GPU at /dev/dri/card1 and display id -1
[GluonDRM] We have 1 connectors
[GluonDRM] ID for connector[0] = 89, width = 1920 and height = 1080
[GluonDRM] Height was 0 and is now 1080
eglinit ok
eglbind ok
eglCreateWindowSurface called
...

If running the sample works but shows a black screen, you need to use vc4-fkms-v3d overlay:

pi@raspberrypi:~ $ sudo nano /boot/config.txt
...
# Enable DRM VC4 V3D driver
#dtoverlay=vc4-kms-v3d
dtoverlay=vc4-fkms-v3d
...

save, and reboot.

If you get this error:

[GluonDRM] use GPU at /dev/dri/card1 and display id -1
[GluonDRM] Device /dev/dri/card1 could be opened, but has no drm capabilities.

you can try with /dev/dri/card0.

From your SSH session, you can click Ctrl+C to finish the process.

X11

If you are running with X11, the command line should be now:

pi@raspberrypi:~ $ sudo -E java --module-path /opt/javafx-sdk-21/lib --add-modules javafx.controls -cp dist/. hellofx.HelloFX
HelloFX app on X11

5.6.4. Run demo app

As a very first demo, we will show how you can run the MaryHadALittleLambda application on a recent Pi, with Java 11 and JavaFX 22-ea.

This application was created in 2014, in order to demonstrate the use of lambda’s. The author, Stephen Chin, wrote a blogpost about this. The application was demonstrated on embedded devices as well.

The original code of the application is at https://github.com/steveonjava/MaryHadALittleLambda. We forked it and made some minor modifications in order to make it work with the JavaFX 11+ and Java 11+ APIs. The new code is at https://github.com/gluonhq/MaryHadALittleLambda.

It is important to mention that you would typically do all your development on your development system (desktop or laptop). You can use your IDE and common tools to create, compile and test applications. Once you are happy with the result, you can transfer your class files over to the embedded device, and run them there.

Download code

The first step is to get the code to your development system and to compile the application.

it is highly recommended doing this on your desktop/laptop, and not on your Raspberry Pi, as it requires much more resources than typically available on embedded devices. However, the Raspberry Pi 4 is a much more powerful device than its predecesors, and it can run the same desktop tasks.
git clone https://github.com/gluonhq/MaryHadALittleLambda.git
cd MaryHadALittleLambda
mvn clean compile

It is good practice to test your application on desktop or laptop before transfering the class files to your device. You can run the application on desktop using:

mvn javafx:run

You should see the application running now.

Sample on desktop

Quit the application, and check if the class files are in the target/classes directory. There should be about 3 class files in the sample package.

5.6.5. Install the class files on the device

You can now copy the classes from your development system to the Raspberry Pi, e.g.

cd target
scp -r classes pi@raspberrypi.local:/home/pi/Downloads
Run the application on the device

Now, log in via SSH to your Raspberry Pi and run the application.

cd ~/Downloads
sudo -E java -Dmonocle.platform=EGL -Dmonocle.egl.lib=/opt/javafx-sdk-21/lib/libgluon_drm-1.1.7.so --module-path /opt/javafx-sdk-21/lib --add-modules javafx.controls -cp classes/ sample.Main
X11

If you use X11, then the demo can be run with:

cd ~/Downloads
sudo -E java --module-path /opt/javafx-sdk-sample.Main/lib --add-modules javafx.controls -cp classes/ sample.Main

5.6.6. Native image for AArch64

We can create native images of Gluon application that run on embedded devices such as the Raspberry Pi, with the only requirement that a 64 bits architecture is currently needed.

In theory, you could install GraalVM for Linux-AArch64 from https://github.com/graalvm/graalvm-ce-builds/releases/, and then run the GluonFX plugin directly on the Pi. However, given its hardware limitation, and the high CPU/memory requirements of the native-image process, the recommended way is building the image from a Linux machine (x86-64).

Ubuntu is the recommended OS to build native images for AArch64 Linux.

In addition to the Linux requirements, "g++ crosscompiler" needs to be installed on the host machine:

sudo apt-get install g++-aarch64-linux-gnu

You also need to install the static version of the DRM library to work without a windows manager. Download it from here:

and copy the file libgluon_drm.a to ~/.gluon/substrate/javafxStaticSdk/21-ea+11.1/linux-aarch64/sdk/lib/.

Finally, add the following profile to the Maven project:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        <target>${gluonfx.target}</target>
        <mainClass>${mainClassName}</mainClass>
        <remoteHostName>pi@raspberrypi.local</remoteHostName>
        <remoteDir>/home/pi/Downloads/native/</remoteDir>
        <runtimeArgs>
            <arg>-Dmonocle.platform=EGL</arg>
            <arg>-Dembedded=monocle</arg>
            <arg>-Dglass.platform=Monocle</arg>
            <arg>-Degl.displayid=/dev/dri/card1</arg>
        </runtimeArgs>
    </configuration>
</plugin>
<profiles>
    <profile>
        <id>pi</id>
        <properties>
            <gluonfx.target>linux-aarch64</gluonfx.target>
        </properties>
    </profile>
</profiles>

The native image can be built using mvn -Ppi gluonfx:build. This will run the compilation phase and link the compiled objects into an executable.

Once the process is finished, you can deploy the binary to your Pi, providing you have correctly defined remoteHostName and remoteDir:

mvn -Ppi gluonfx:install

The install goal makes use of scp and ssh commands to transfer the native image, so SSH must be enabled on the remote device. It is convenient to add the SSH public key of your developing machine to the device, so these commands won’t prompt for password:

ssh-copy-id pi@raspberrypi.local

You can run from your machine, via SSH, using:

mvn -Ppi gluonfx:nativerun

making sure the runtime arguments are properly defined in the pom’s configuration as shown above. To finish the process, press Ctrl+C.

Or alternatively, you can you can run the native image directly on your Pi from command line with:

pi@raspberrypi:~ $ export ENABLE_GLUON_COMMERCIAL_EXTENSIONS=true
pi@raspberrypi:~ $ cd ~/Downloads/native
pi@raspberrypi:~ $ sudo -E ./HelloFX -Dmonocle.platform=EGL -Dembedded=monocle -Dglass.platform=Monocle -Degl.displayid=/dev/dri/card1

The UI output will be directly sent to the framebuffer, leveraging the KMS/DRM components in the Linux kernel and low-level graphical drivers.

Or if you run from a terminal in X11:

pi@raspberrypi:~ $ export ENABLE_GLUON_COMMERCIAL_EXTENSIONS=true
pi@raspberrypi:~ $ cd ~/Downloads/native
pi@raspberrypi:~ $ sudo -E ./HelloFX

In this case, the UI will be attached to the running X server.

5.7. Web

Experimental

Web port is currently experimental and under active development.

Gluon applications can be compiled to run on the browser. Cross compilation is supported on Linux, Mac and Windows.

5.7.1. HelloFX Sample

Clone HelloFX on your system.

CSS is currently not supported. Any instance of adding stylesheets needs to be removed/commented out from the source.

The main method in HelloFX needs to be updated to pass Application class to the launch method:

public static void main(String[] args) {
    launch(HelloFX.class, args);
}

Add the target to web via a new maven profile web:

<profiles>
   ...
    <profile>
        <id>web</id>
        <properties>
            <gluonfx.target>web</gluonfx.target>
        </properties>
    </profile>
</profiles>

Once the profile is in place, cross-compilation can be initiated using:

mvn -Pweb gluonfx:build

It produces the index.html and a number of required javascript files under target/gluonfx/x86_64-web/gvm/web.

Gluon web port currently supports Chrome, Chromium or Firefox.

mvn -Pweb gluonfx:nativerun
HelloFX Web running

5.7.2. HelloWeb Sample

HelloWeb is another sample which shows JavaFX animation on web. The samples run a series of animation on different nodes.

Clone HelloWeb on your system.

The web profile along with changes required for main method are already in place.

To run the sample in web, execute:

mvn -Pweb gluonfx:build gluonfx:nativerun

6. User Interface (UI)

Glisten is the UI toolkit of Gluon framework. It contains base APIs for the UI toolkit, including APIs for controls and animation. It offers cross-platform behavior with a platform specific look and feel, based on Material design specification.

Javadoc

Glisten Javadoc contains API documentation for Gluon Mobile UI toolkit, controls and animation.

6.1. Glisten APIs

6.1.1. MobileApplication

MobileApplication class extends from Application, hence for Glisten-based applications it will be considered the base class.

Let’s have a look at the hierarchy of components added to generate the user interface.

GlassPane

GlassPane is the root node added automatically by Glisten to the primary scene.

It can be seen as the invisible container for all the nodes that will be added: views, layers, dialogs or toolbars.

Usually, there won’t be any need to access this container directly. But it can be retrieved with MobileApplication.getInstance().getGlassPane(), if needed.

The first children of the GlassPane container will always be a View and the AppBar.

MobileApp

The nodes on top will be layers (like a side menu), popups or dialogs.

In between, there is a semi-transparent node that will ensure that the node on top appears distinct from the content beneath it, whenever this is shown. Otherwise it won’t be visible.

MobileAppLayer
AppBar

AppBar is a special node acting as a toolbar for branding, navigation, search and other actions.

It is placed at the top of the layout and is generally made up of some buttons (nav icon and action items), a title and a menu.

The content (in terms of nodes and their respective actions) will be managed directly by each of the different views, so the AppBar will be like a toolbar placeholder for each one of them.

Typically, the developer sets up the AppBar for a given View by overriding its updateAppBar method:

public class HomeView extends View {

    public HomeView() {
        FloatingActionButton fab = new FloatingActionButton();
        fab.showOn(this);
    }

    @Override
    protected void updateAppBar(AppBar appBar) {
        appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> System.out.println("nav icon")));
        appBar.setTitleText("The AppBar");
        appBar.getActionItems().addAll(
            MaterialDesignIcon.SEARCH.button(e -> System.out.println("search")),
            MaterialDesignIcon.FAVORITE.button(e -> System.out.println("fav")));
        appBar.getMenuItems().addAll(new MenuItem("Settings"));
    }
}
TheAppBar

If the developer doesn’t require the AppBar for a given view, it can be hidden by setting its visibility to false: appBar.setVisible(false).

Another way to update the AppBar will be adding a listener to each view’s showingProperty:

public class MyApp extends MobileApplication {

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> {
            View view = new View(new Label("Hello Glisten!"));
            view.showingProperty().addListener((obs, oldValue, newValue) -> {
                if (newValue) {
                    AppBar appBar = MobileApplication.getInstance().getAppBar();
                    appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> System.out.println("nav icon")));
                }
            });
            return view;
        });
    }
}
TheAppBar2
View

View is a Glisten container that will allow adding nodes in its top, left, right, bottom, and center positions.

Usually a View instance is created by providing a node for its content. This instance is added to a factory of views with a name, so the Glisten UI can load and unload them on demand.

Home View

By default, the home view will be the first view displayed when the stage is shown.

It has no predefined content, so this view has to be designed by the developer, but its name is already assigned: MobileApplication.HOME_VIEW.

This short snippet will create a very simple mobile application with a single view:

public class MyApp extends MobileApplication {

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> new View(new Label("Hello Glisten!")));
    }

}
FirstView

6.1.2. How it works

In order to understand how Glisten works, let’s have a look at the initial JavaFX application lifecycle. Whenever an application is launched, the JavaFX runtime does the following:

  • Constructs an instance of the specified Application class

  • Calls the init() method

  • Calls the start(javafx.stage.Stage) method

Typically, the developer of a Glisten application will only override the init() method, in order to add one or more View objects, and provide them as factories that can be called on-demand. The MobileApplication implementation will take care of the rest: when start() is called, it will create a Scene, adding a root node to it. Finally, it will put the scene in the primary stage and show it.

When the runtime calls init(), we just provide a Supplier<View> for HOME_VIEW, but the view is not instantiated at this point yet.

Then start(Stage) is called. At this moment, MobileApplication creates an instance of Scene, sets the root an empty instance of a GlassPane and adds the scene to the primary stage.

After some internal settings, like adding the default Swatch, it calls switchView("HOME_VIEW"). This is the moment when an instance of the home view is created and added to the pane.

Finally, the stage is shown.

Changing the default Swatch

What if we want to modify some scene settings before it is shown?

Before showing the stage, there’s a call to the postInit(Scene) method, that can be used by the developer for one time initialization.

public class MyApp extends MobileApplication {

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> new View(new CheckBox("I like Glisten")));
    }

    @Override
    public void postInit(Scene scene) {
        Swatch.GREEN.assignTo(scene);
    }

}
FirstViewGreen

In case the developer wants to change the swatch for each view, this can be done by listening to the changes in the viewProperty of a MobileApplication instance, and assigning the corresponding Swatch:

public class MyApp extends MobileApplication {

    private static final String OTHER_VIEW = "other";

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> new View(new Label("Hello Glisten!")) {
            {
                FloatingActionButton fab = new FloatingActionButton(MaterialDesignIcon.ADD_BOX.text, e -> switchView(OTHER_VIEW));
                fab.showOn(this);
            }
        });
        addViewFactory(OTHER_VIEW, () -> new View(new CheckBox("I like Glisten")) {
            {
                FloatingActionButton fab = new FloatingActionButton(MaterialDesignIcon.ADD_CIRCLE_OUTLINE.text, e -> switchView(HOME_VIEW));
                fab.showOn(this);
            }
        });

        viewProperty().addListener((obs, oldView, newView) -> {
            switch(newView.getName()) {
                case HOME_VIEW:
                    Swatch.INDIGO.assignTo(newView.getScene());
                    break;
                case OTHER_VIEW:
                    Swatch.GREEN.assignTo(newView.getScene());
                    break;
                default:
                    Swatch.getDefault().assignTo(newView.getScene());
            }
        });
    }
}

Another way to accomplish the same will be by adding a listener to each view’s showingProperty:

public class MyApp extends MobileApplication {

    private static final String OTHER_VIEW = "other";

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> {
            View view = new View(new RadioButton("Glisten style"));
            FloatingActionButton fab = new FloatingActionButton(MaterialDesignIcon.ADD_BOX.text, e -> switchView(OTHER_VIEW));
            fab.showOn(view);
            view.showingProperty().addListener((obs, oldValue, newValue) -> {
                if (newValue) {
                    Swatch.RED.assignTo(view.getScene());
                }
            });
            return view;
        });
        addViewFactory(OTHER_VIEW, () -> {
            View view = new View(new CheckBox("I like Glisten"));
            FloatingActionButton fab = new FloatingActionButton(MaterialDesignIcon.ADD_CIRCLE_OUTLINE.text, e -> switchView(HOME_VIEW));
            fab.showOn(view);
            view.showingProperty().addListener((obs, oldValue, newValue) -> {
                if (newValue) {
                    Swatch.TEAL.assignTo(view.getScene());
                }
            });
            return view;
        });
    }
}
FirstViewRed
Creating a View

As we have already seen in the previous code snippets, there are several possible ways to create a View.

Extending View

A custom view can be created by extending View. This allows for more complex views, for adding a view transition, customizing the AppBar…​

public class MyApp extends MobileApplication {

    private static final String OTHER_VIEW = "other";

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> new HomeView());
        addViewFactory(OTHER_VIEW, () -> new View(new CheckBox("I like Glisten")));
    }

    @Override
    public void postInit(Scene scene) {
        Swatch.LIGHT_GREEN.assignTo(scene);
    }

    class HomeView extends View {

        public HomeView() {
            setCenter(new Label("Hello Glisten!"));
        }

        @Override
        protected void updateAppBar(AppBar appBar) {
            appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> switchView(OTHER_VIEW)));
            appBar.setTitleText("Home View");
        }
    }
}
HomeView
Adding content

A View can be created by using one of its constructors, where the content is set. Any Node will work as content for the View.

public class MyApp extends MobileApplication {

    private static final String OTHER_VIEW = "other";

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> new View(new Home()) {
            @Override protected void updateAppBar(AppBar appBar) {
                appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> switchView(OTHER_VIEW)));
                appBar.setTitleText("Home View");
            }
        });
    }

    class Home extends VBox {

        public Home() {
            setSpacing(30);
            setAlignment(Pos.CENTER);
            getChildren().addAll(new Label("Hello Glisten!"), new Button("Click me"));
        }
    }
}
VBoxView
Creating Views with FXML

View can be used within FXML, by adding the proper import.

<?xml version="1.0" encoding="UTF-8"?>

<?import com.gluonhq.charm.glisten.mvc.View?>
<?import javafx.scene.control.Button?>

<View fx:id="home" prefHeight="400.0" prefWidth="600.0" styleClass="mainFxmlClass" xmlns="http://javafx.com/javafx/11" xmlns:fx="http://javafx.com/fxml/1">
   <center>
      <Button fx:id="button" onAction="#onClick" text="Click me!" />
   </center>
</View>

Load the fxml file using the FXMLLoader. Assuming the home.fxml file is under the same package as the application, but in the 'resources' directory:

public class MyApp extends MobileApplication {

    private static final String OTHER_VIEW = "other";

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> {
            try {
                return (View)FXMLLoader.load(getClass().getResource("views/home_1.fxml"));
            } catch (Exception ex) { }
            return null;
        });
        addViewFactory(OTHER_VIEW, () -> new View(new CheckBox("I like Glisten")));
        viewProperty().addListener((obs, ov, nv) -> {
            AppBar appBar = MobileApplication.getInstance().getAppBar();
            switch(nv.getName()) {
                case HOME_VIEW:
                    appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> switchView(OTHER_VIEW)));
                    appBar.setTitleText("Home View");
                    Swatch.TEAL.assignTo(appBar.getScene());
                    break;
                case OTHER_VIEW:
                    appBar.setVisible(false);
                    break;
            }
        });
    }
}
FXMLView
Using Gluon’s Scene Builder

View is available under the Gluon titled pane on the top left of Scene Builder.

SceneBuilder

Note: Prior to Scene Builder 8.3.0, charm-glisten-6.2.3.jar should be imported into the Scene Builder.

Afterburner framework

Adding the dependency to the build.gradle script, allows using this MVP framework from Adam Bien.

dependencies {
    compile 'com.airhacks:afterburner.mfx:1.6.3'
}

For every view, these files are required:

In the code folder:

  • The view i.e. HomeView. An empty class that just extends FXMLView.

  • The presenter. I.e. HomePresenter.

  • A service (optional), i.e. HomeService.

And in the resources folder:

  • The fxml, i.e. home.fxml

  • The css (optional), i.e. home.css.

In the presenter, controls can be added to the view, like a Button to trigger some action when the user clicks on it.

A transition can be set for switching views from a large list of available transitions (see the Charm JavaDoc). In case no transition is desired, the developer can select NoTransition().

<View fx:id="home" xmlns="http://javafx.com/javafx/11" xmlns:fx="http://javafx.com/fxml/1" fx:controller="HomePresenter">
    <center>
        <StackPane>
            <Button text="Click!" onAction="#onClick"/>
        </StackPane>
    </center>
</View>

The presenter can be created to directly interact with the nodes in the FXML.

public class HomePresenter implements Initializable {

    @FXML
    private View home;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        FloatingActionButton fab = new FloatingActionButton();
        fab.setOnAction(e -> {
            System.out.println("FAB click");
        });
        fab.showOn(home);

        home.setShowTransitionFactory(v -> new FadeInLeftBigTransition(v));

        home.showingProperty().addListener((obs, ov, nv) -> {
            if (nv) {
                final AppBar appBar = MobileApplication.getInstance().getAppBar();
                appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> System.out.println("menu")));
                appBar.setTitleText("The Home View");
                appBar.getActionItems().addAll(
                        MaterialDesignIcon.SEARCH.button(),
                        MaterialDesignIcon.FAVORITE.button());
                appBar.getMenuItems().addAll(new MenuItem("Settings"));

                Swatch.PURPLE.assignTo(home.getScene());
            }
        });
    }

    @FXML
    public void onClick() {
       System.out.println("click");
    }
}

Then, a view can be created:

public class HomeView extends FXMLView {

}

This View can be loaded in a MobileApplication:

public class MyApp extends MobileApplication {

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> {
            final HomeView homeView = new HomeView();
            return (View) homeView.getView();
        });
    }
}
Afterburner

In order to perform some actions while the transition is running or when it finishes, a custom ActionEvent can be provided to the end of the transition.

This can be done as well based on the LifecycleEvent events. The following code snippet will set the view transparent to mouse clicks just during the time it is added to the scene and the transition ends.

public class MyApp extends MobileApplication {

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> {
            final HomeView homeView = new HomeView();
            final View view = (View) homeView.getView();
            view.addEventHandler(LifecycleEvent.SHOWING, e ->
                view.setMouseTransparent(true));
            view.addEventHandler(LifecycleEvent.SHOWN, e ->
                view.setMouseTransparent(false));
            return view;
        });
    }
}

6.1.3. Layers

A Layer is an overlay that can be shown above any View.

Layers can be provided as factories by using MobileApplication.getInstance().addLayerFactory(), and can be shown on demand by using MobileApplication.getInstance().showLayer().

public class MyApp extends MobileApplication {

    @Override
    public void init() {
        addViewFactory(HOME_VIEW, () -> new View(new Label("Hello Glisten!")) {
            @Override
            protected void updateAppBar(AppBar appBar) {
                appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> showLayer("New Layer")));
            }
        });

        addLayerFactory("New Layer", () -> {
            final StackPane stackPane = new StackPane(new Button("Click"));
            stackPane.setStyle("-fx-background-color: white; -fx-padding: 10;");
            SidePopupView sidePopupView = new SidePopupView(stackPane);
            return sidePopupView;
        });
    }
}

Layers can also be be shown and hidden, without the use of factories, by calling show() and hide() respectively.

SidePopupView sidePopupView = new SidePopupView(stackPane);
sidePopupView.show();

By calling setBackgroundFade(double) the developer will be able to fade the background of a layer to a darker color, obscuring the main application and drawing focus to the popup.

Layer1
Creating a Layer

Developers can create custom Layer nodes. This is a minimal implementation, including a faded background when the layer is shown.

public class MyApp extends MobileApplication {

    @Override
    public void init() {

        addViewFactory(HOME_VIEW, () -> new View(new Label("Hello Glisten!")) {
            @Override
            protected void updateAppBar(AppBar appBar) {
                appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> showLayer("New Layer")));
            }
        });

        addLayerFactory("New Layer", () -> new Layer() {
            private final Node root;
            private final double size = 150;
            final GlassPane glassPane = MobileApplication.getInstance().getGlassPane();

            {
                setBackgroundFade(0.5);
                root = new StackPane(new Button("A custom layer"));
                root.setStyle("-fx-background-color: white;");
                getChildren().add(root);
            }

            @Override
            public void layoutChildren() {
                super.layoutChildren();
                root.setVisible(isShowing());
                if (!isShowing()) {
                    return;
                }
                root.resize(size, size);
                resizeRelocate((glassPane.getWidth() - size)/2, (glassPane.getHeight()- size)/2, size, size);
            }
        });
    }
}

Layers can also be created and shown without using the factory methods.

public class MyApp extends MobileApplication {

    private Layer layer;

    @Override
    public void init() {

        addViewFactory(HOME_VIEW, () -> new View(new Label("Hello Glisten!")) {
            @Override
            protected void updateAppBar(AppBar appBar) {
                appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> {
                    if (layer == null) {
                        layer = createLayer();
                    }
                    layer.show();
                }));
            }
        });
    }

    private Layer createLayer() {
        return new Layer() {
            private final Node root;
            private final double size = 150;
            final GlassPane glassPane = MobileApplication.getInstance().getGlassPane();

            {
                setBackgroundFade(0.5);
                root = new StackPane(new Button("A custom layer"));
                root.setStyle("-fx-background-color: white;");
                getChildren().add(root);
            }

            @Override
            public void layoutChildren() {
                super.layoutChildren();
                root.setVisible(isShowing());
                if (!isShowing()) {
                    return;
                }
                root.resize(size, size);
                resizeRelocate((glassPane.getWidth() - size)/2, (glassPane.getHeight()- size)/2, size, size);
            }
        };
    }
}
CustomLayer

However, Glisten already provides a series of built-in layers, so creating custom layers won’t be necessary in most of the occasions.

Using built-in layers

Glisten uses layers to show additional information. The most commonly used layers are:

  • MenuPopupView

  • MenuSidePopupView

  • PopupView

  • SidePopupView

For a description of each layer, check the API documentation: Glisten layers.

Many Glisten controls use layers internally, including FloatingActionButton, NavigationDrawer, Snackbar and Toast.

6.1.4. UI Controls

Glisten comes with a collection of cross platform UI controls based on the Material Design Specification: Glisten Controls.

6.2. Glisten CSS Overview

An overview of the CSS style classes and properties that are available to users of the Gluon Charm Glisten library. Users of Glisten CSS should be familiar with the JavaFX CSS functionality.

6.2.1. Glisten Themes

Glisten ships with two CSS themes: light and dark. Despite the connotation, the amount of CSS required to specify these themes is incredibly minimal - and as a courtesy is reproduced here:

Light Theme
.root {
  -fx-background: white;
  -fx-text-fill: -text-light;
  -fx-prompt-text-fill: rgba(0,0,0,.3);

  -text-disabled: -text-disabled-light;
  -background-disabled:rgba(0,0,0,.12);
  -background-fill: -text-light;

  -flat-button-hover-background: rgba(#999999,.2);
  -flat-button-pressed-background: rgba(#999999,.4);
  -flat-button-disabled-fill: rgba(#000000,.26);
}
Dark Theme
.root {
  -fx-background: #333333;
  -fx-text-fill: -text-dark;
  -fx-prompt-text-fill: rgba(255,255,255,.3);

  -text-disabled: -text-disabled-dark;
  -background-disabled:rgba(255,255,255,.12);
  -background-fill: -text-dark;

  -flat-button-hover-background: rgba(#CCCCCC,.15);
  -flat-button-pressed-background: rgba(#CCCCCC,.25);
  -flat-button-disabled-fill: rgba(#FFFFFF,.3);
}

6.2.2. Glisten CSS Properties

There are a number of global properties that can be used by any application that uses Glisten. These properties allow for custom controls to be styled in a way that should relatively closely match up with the Glisten-styled controls. What follows is a list of such properties.

Swatches

Perhaps most importantly, Glisten supports swatches, where each swatches populates a series of pre-specified CSS properties which can be used within your own CSS. The properties that are populated for each swatch are the following:

-primary-swatch-50
-primary-swatch-100
-primary-swatch-200
-primary-swatch-300
-primary-swatch-400
-primary-swatch-500
-primary-swatch-600
-primary-swatch-700
-primary-swatch-800
-primary-swatch-900
-alternate-swatch-100
-alternate-swatch-200
-alternate-swatch-400
-alternate-swatch-700

Each of these properties is simply a color along a certain color spectrum. All swatches provided by Glisten are based on the Material Design color style guide. The Glisten CSS files do not typically make use of all swatch colors, so it is also advised that any use of these colors be restrained, outside of the -primary-swatch-500, and possibly -primary-swatch-200, -primary-swatch-600, and -primary-swatch-700.

An example of how such a property could be used is shown in the sample code below:

.button {
  -fx-background-color: -primary-swatch-500;
}

.button:hover {
  -fx-background-color:-primary-swatch-600;
}
Other Properties

There are a number of other properties that are available to use. Some of these are custom Glisten properties, but many are standard JavaFX CSS properties that Glisten simply modifies to the appropriate value. Some notable properties include the following (refer to the JavaFX CSS Reference Guide for more details of any that start with -fx-) :

  • -text-light: This is a light-colored text, best used on a dark background.

  • -text-dark: This is a dark-colored text, best used on a light background.

  • -fx-text-fill: This is populated based on the theme that is currently set (i.e. light or dark).

  • -text-disabled: This is used in controls where the control is disabled.

Text Fill

It is important to understand how text fill works in Glisten, as the underlying JavaFX CSS engine is significantly more powerful than normal CSS engines. In particular, when it comes to deciding what color text fill to use, you can use JavaFX CSS laddering to decide. In other words, it is never a good idea to use -fx-text-fill directly, as -fx-text-fill does not take into account the background color behind the text (so it is possible that the text is unreadable).

In cases where a custom text fill is desired, it is much better to do something such as the following:

.button {
  -fx-background-color: -primary-swatch-500;
  -fx-text-fill: ladder(-primary-swatch-500, -text-dark 49%, -text-light 50%)
}

This code states to use -text-dark or -text-light, depending on the darkness of the first value, in this case -primary-swatch-500. The result of this is that when -primary-swatch-500 is a very light color (such as a bright yellow), the -fx-text-fill will be -text-light (and therefore appear as a dark color). When -primary-swatch-500 is a very dark color, -text-dark is used, and is therefore a light color that is readable on a dark background.

6.2.3. Glisten Style Classes

By default, when a UI control is instantiated in Glisten, the control does not receive any additional style classes over what is provided by the underlying JavaFX technology. However, depending on the use case for each individual control, it can make sense to apply additional style classes to have Glisten apply additional styling to these controls.

There exists a class called GlistenStyleClasses that is located in the com.gluonhq.glisten.visual package. This class provides a number of predefined style classes that can be applied to a control, via one of two methods:

1. JavaFX StyleClass List API:
final ToggleButton toggleVisibilityButton = new ToggleButton("Toggle Visibility");

/*
 * TOGGLE_BUTTON_SWITCH is a static import from GlistenStyleClasses.
 * It is simply a String consisting of 'switch'.
 */
toggleVisibilityButton.getStyleClass.add(TOGGLE_BUTTON_SWITCH);
2. GlistenStyleClasses Convenience API:
final ToggleButton toggleVisibilityButton = new ToggleButton("Toggle Visibility");

/*
 * Both applyStyleClass and TOGGLE_BUTTON_SWITCH are static
 * imports from GlistenStyleClasses
 */
applyStyleClass(toggleVisibilityButton, TOGGLE_BUTTON_SWITCH);

Both approaches are more or less equivalent, but the second approach is recommended.

Once these additional style classes have been applied, both Glisten CSS and your own CSS can be applied as applicable. For example, a button that has been styled to be flat (using GlistenStyleClasses.BUTTON_FLAT) will be able to receive alternate styling via css such as the following:

.button.flat {
  ...
}

Of course, as noted, any styling of UI controls based on style classes from the GlistenStyleClasses class will be styled differently by Glisten CSS, so there is no need (unless desired) to apply additional styling.

What follows is a list of additional CSS style classes that are available to some UI controls:

Button

Buttons have the CSS style class .button. There are the following additional style classes that are available in Glisten:

Style Class

GlistenStyleClasses Property

Description

flat

BUTTON_FLAT

The flat style class results in a button that is represented with a visually 'flat' style

round

BUTTON_ROUND

The round style class causes the button to be rounded (for example, think of the commonly-used 'floating action button' that is present in the Material Design documentation).

Toggle Button

Toggle buttons have the CSS style class .toggle-button.

Style Class

GlistenStyleClasses Property

Description

switch

TOGGLE_BUTTON_SWITCH

The switch style class results in toggle button that matches the Material Design Switch representation.

6.2.4. Other Questions

If this document does not answer all your questions regarding Glisten CSS functionality, please reach out to Gluon staff and ask any questions. They will help us to improve future versions of this document.

6.3. Release Notes

This page provides information about the releases of Charm Glisten, including known issues, limitations, and general advisories. Please review the notes before using the library.

To discuss issues or ideas with other developers, please reach out to us.

6.1.0

Glisten 6.1.0 deprecates MobileApplication and provides a new AppManager that will manage the JavaFX Application flow and integrate the Glisten features.

The following code snippet shows the implementation of a typical Application class that includes the application manager:

public class MyApplication extends Application {

    private final AppManager appManager = AppManager.initialize(this::postInit);

    @Override
    public void init() throws Exception {
        appManager.addViewFactory(HOME_VIEW, () -> new View() {...});
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        appManager.start(primaryStage);
    }

    private void postInit(Scene scene) {
        Swatch.BLUE.assignTo(scene);
    }

}

This change is backward compatible, and you can still use Charm 6.1.0+ with MobileApplication, which will be marked as deprecated on your IDE.

If you have an existing project, you can simply bump your Glisten dependencies, without needing to modify your code.

If you use the Glisten-Afterburner framework, GluonPresenter has been deprecated too. Any presenter class doesn’t need to extend from GluonPresenter<Application>, but it is still supported for compatibility reasons.

However, it is advisable to adapt to the new API and introduce the AppManager as shown above or in the different Gluon Samples.

Migration guidelines

The required steps to do the migration are:

Application class
  • Change MobileApplication to Application

  • Add the AppManager and initialize it, passing the postInit callback.

  • Remove the @Override annotation from the postInit method, and optionally make it private.

  • Override the Application::start method, which should simply call the start method of the application manager and pass it the stage parameter.

  • The home view name should be taken from AppManager.HOME_VIEW, and the view factories can be added from AppManager::addViewFactory (same for AppManager::addLayerFactory).

  • If you use an AppViewManager to manage the view creation, you don’t need to pass it the Application instance, as the AppManager instance can be always accessed via AppManager.getInstance().

Presenter class
  • Remove extends GluonPresenter<YourApplication>

  • Replace the calls to getApp() with AppManager.getInstance().

6.0.0

Glisten 6.0.0 comes with JavaFX 11 support.

5.0.0

Glisten 5.0.0 release has a significant number of breaking changes. For a detailed document on how to migrate to 5.0.0, please refer Migration Guide to Gluon Mobile 5.

New Features
  • MobileApplication exposes a new method getDrawer() which should be used to access the application wide NavigationDrawer

  • New control Snackbar replaces SnackbarPopupView

  • MobileTransition has been added to pause and resume animation along with the change in the life-cycle of the mobile application

  • View has defined center as the default property, which enables setting a center child in fxml without using the <center> tag

  • Layer is managed using life-cycle. They are four life-cycle events: showing, shown, hiding, hidden.

  • Layers can now be shown on MobileApplication without adding them to GlassPane or View.

  • NavigationDrawer has been improved to automatically show and hide without the need to add it to a SidePopupView.

  • NavigationDrawer closes automatically when an item is selected

  • Support for notch in iPhone X devices.

Package Changes
  • FAB has been moved from com.gluonhq.charm.glisten.layout.layer to com.gluonhq.charm.glisten.control

API changes
  • MobileLayoutPane has been removed

  • GlassPane and View extend from BorderPane instead of MobileLayoutPane

  • GlassPane and View no longer expose the list of layers. The method getLayers() has been removed

  • View no longer exposes a name property

  • View’s show transition factory has been changed from Function<View,Transition> to Function<View,MobileTransition>

  • Layers can be shown using the new API. In order to show and hide a layer, call show() and hide() respectively

  • ActionItems in BottomNavigation only accept instances of BottomNavigationButton instead of Node

  • Deprecated method createButton() has been removed from BottomNavigation

  • New constructor has been added to most of the Transitions to help developers define custom duration

  • PopupView gives more control to the developer by allowing them to control the side and padding of the popup. Additionally, height and width of the popup can now be changed using the pref*, min* and max* properties.

  • PopupView has dropped autofix property. AutoFix is now set to true for all usage of PopupView.

  • AutoCompleteTextField has a new converter for easy conversions between strings and objects.

  • FAB can bind to a View using showOn(View view)

  • New methods have been added to NavigationDrawer to open and close the drawer - open() and close()

Bug Fixes
  • MenuPopupView and AutoCompletePopup support text wrapping

  • AutoCompleteTextField selection of a node from the popup, converts the selected object to a string and shows it in the TextField.

  • Animation duration for PopupView and SidePopupView has been reduced to match Material Design specification

  • Floating text translates to the top when TextField has text, irrespective of state of focus property

  • Nag window no longer throws exception when no home view has been defined

  • ToggleButtonGroup minWidth should never outgrow prefWidth

  • SettingsPane is updated after any change in the options list

  • Layer has been updated to fix multiple issues

  • Javadoc has been updated to fix multiple issues

7. Device Interface

Open Source

Attach is open source, and licensed under the GPL license. Its source code is hosted under Gluon organization in Github.

Gluon Attach addresses the integration with low-level platform APIs. Using Attach, you write code that accesses device and hardware features using a uniform, platform-independent API. At runtime, the appropriate implementation (desktop, android, ios) makes sure the platform specific code is used to deliver the functionality.

The following sections provide a high-level overview of the current Attach features.

More information can be found in the Gluon website, and you can find different samples using different Attach services.

This library is being actively extended, so if you think an important feature is missing, feel free to log an issue or contribute a pull request at our Attach repository.

Attach project is split up into different services. Each service takes care of implementing a certain hardware or device feature, like access to general device information, information about the display, outputs of different sensors that are found on the device, etc.

The full list of services can be found here.

7.1. Using Attach

7.1.1. Prerequisites

Attach prerequisites are in line with those required by each Platform for developing Gluon application.

7.1.2. Adding Attach to the project

Configuring your project to include an Attach service is done via adding the dependency to the project and the name of the service to attachList configuration.

When creating a project with the Gluon IDE plugin, the attachList is already available and pre-configured with the following four services: display, lifecycle, statusbar and storage.

The list of services can be extended with any of the available services.

For instance, the Position service, that allows accessing the device’s GPS, can be added as follows:

<dependency>
    <groupId>com.gluonhq.attach</groupId>
    <artifactId>position</artifactId>
    <version>${attach.version}</version>
</dependency>
...
<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        <target>${gluonfx.target}</target>
        <attachList>
            ...
            <list>position</list>
        </attachList>
        <mainClass>${main.class}</mainClass>
    </configuration>
</plugin>

7.1.3. Using Services

Each Attach service provides the functionality to access a given feature in every platform where it is supported.

Each service can be accessed via its create() method, which returns an Optional which contains an instance of the requested service.

The returned optional object might be empty if the runtime platform has no available implementation for that specific service.

PositionService positionService = PositionService.create().orElseThrow(() -> new RuntimeException("PositionService not available."));
positionService.positionProperty().addListener((obs, ov, nv) -> {
    System.out.println("Latest known GPS coordinates from device: " + nv.getLatitude() + ", " + nv.getLongitude());
});
positionService.start();

Another useful class is the enum com.gluonhq.attach.util.Platform, which provides an easy way to detect what platform the application is currently running on: ANDROID, IOS or DESKTOP.

7.2. Services Overview

For an overview of all the available services, we refer you to the services package overview page in the Attach javadoc:

7.2.1. Service Requirements

Each service has certain requirements that need to be addressed before deploying the mobile applications, either on Android, or on iOS, or both.

Attach javadoc provides detailed information on these cases.

Android

For instance, the Dialer service requires a CALL_PHONE permission included in the Android manifest file:

<manifest ...>
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    ...
</manifest>

The AndroidManifest.xml file is generated for the project with the gluonfx:package goal, and it is available at target/gluonfx/aarch64-android/gensrc/android.

GluonFX plugin takes care of most of these modifications, and usually, there is no need to modify this manifest.

Only in case you need to make any change, copy this file to src/android and make any modification that might be needed. For every new run of gluonfx:package, the manifest found at src/android will be used.

iOS

For instance, the Position service requires a few keys included in the Default-Info.plist file:

<plist ...>
<dict>
    ...
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>This app needs location services</string>
    <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
    <string>This app needs location services</string>
    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>This app needs bluetooth services</string>
</dict>
</plist>

The Default-Info.plist file is generated for the project with the gluonfx:link goal, and it is available at target/gluonfx/arm64-ios/gensrc/ios.

GluonFX plugin takes care of most of these modifications, and usually, there is no need to modify this plist file.

Only in case you need to make any change, for instance, adding a new key or modifying the description of an existing one, you can create a partial plist file under src/main/resources/META-INF/substrate/ios/Partial-Info.plist, and apply the required changes, like:

<?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>NSBluetoothAlwaysUsageDescription</key>
    <string>My custom description</string>
</dict>
</plist>

Alternatively, you can copy target/gluonfx/arm64-ios/gensrc/ios/Default-Info.plist into src/ios and make any modification that might be needed. For every new run of gluonfx:link, the plist found at src/ios will be used.

7.3. Building Attach

The following sections are intended only for developers who want to create a native service or understand how the Attach native services are created.

If you want to build Attach from code, you need JDK 11+, Xcode 11+, the Android SDK 29 and the Android NDK.

Clone the repository, and from Mac OS X, run:

export ANDROID_SDK=$ANDROID_HOME
export ANDROID_NDK=$ANDROID_HOME/ndk-bundle
./gradlew clean build publishToMavenLocal

It will build all services for all possible platforms (desktop, iOS and Android), and it will install them to your .m2 local repository.

You can try to build from Linux or Windows, however the iOS implementation won’t be built.

Single service builds can be done as well. For instance, to build and install locally the Display service, run:

./gradlew :display:publishToMavenLocal

7.3.1. Platform builds

The tasks under mavenPublish.gradle and native-build.gradle perform the native build for desktop, iOS and Android.

The goal is to produce an artifact that can be used as dependency by the GluonFX plugin when creating a native image that will be deployed to the target platform.

Such artifact (a jar file), bundles Java classes, configuration files and native libraries.

The builds are done with the nativeBuild task. For instance, to build the native libraries for the three platforms, run:

./gradlew :display:nativeBuild

or in case you want to build a given platform, run:

./gradlew :display:iosBuild
Desktop

For a given service, its Java code (GraalVM) is compiled against JDK 11+, and with the desktopJar task it is added to the ${service}-${version}-desktop.jar file.

Reflection and jni configuration json files are added under META-INF/substrate/config (see config files).

Native code, if present, will be compiled, and the native library will be added to the jar to a native folder.

iOS

For a given service, its Java code (GraalVM) is compiled against JDK 11+, and with the iosJar task it is added to the ${service}-${version}-ios.jar file.

Reflection and jni configuration json files are added under META-INF/substrate/config (see config files).

Native code using Objective-C and found for each service under:

def osSources = []
osSources = "$projectDir/src/main/native/ios/${name}.m"

is compiled and linked, using the iOS SDK with certain flags and iOS frameworks, to create a native library under:

def linkerOutput = "$buildDir/native/ios/$arch/lib${name}.a"

The native library is then added to the jar to a native folder.

Android

For a given service, its Java code (GraalVM) is compiled against JDK 11+, and with the androidJar task it is added to the ${service}-${version}-android.jar file.

Reflection and jni configuration json files are added under META-INF/substrate/config (see config files).

Native code using C and found for each service under:

def nativeSourcesDir = []
nativeSourcesDir = "$projectDir/src/main/native/android/c/*.c"

is compiled and linked, using the Android SDK with certain flags, to create a native library under:

def linkerOutput = "$buildDir/native/android/lib${name}.a"

The native library is then added to the jar to a native folder.

Finally, Java code for Android (Dalvik) is compiled against JDK 1.7, using the Android SDK. An Android library project is generated and bundled under META-INF/substrate/dalvik/${name}.aar. It will contain mainly a jar with classes and an AndroidManifest.xml file with the service requirements (like permissions or activities).

7.3.2. Attach with GluonFX plugin

An Attach service is added to a project as regular dependency but it has also to be included in the attachList:

<!-- dependencies -->
<dependency>
    <groupId>com.gluonhq.attach</groupId>
    <artifactId>display</artifactId>
    <version>4.0.19</version>
</dependency>

<!-- plugin -->
<configuration>
    <attachList>
        <list>display</list>
    </attachList>
</configuration>

If needed, the platform classifier can be used to run the project on HotSpot with the JavaFX Maven plugin:

<!-- dependencies -->
<dependency>
    <groupId>com.gluonhq.attach</groupId>
    <artifactId>display</artifactId>
    <version>4.0.19</version>
</dependency>
<dependency>
    <groupId>com.gluonhq.attach</groupId>
    <artifactId>display</artifactId>
    <version>4.0.19</version>
    <classifier>desktop</classifier>
    <scope>runtime</scope>
</dependency>

This can be done with the Maven profiles, so these platform dependencies are only activated for the correct profile.

When running the GluonFX plugin goals, for each Attach service, the plugin will apply the correct classifier based on the target and resolve the artifact.

Then, for every ${service}-${version}-${platform}.jar file found in the classpath, the plugin will:

  • add the Java classes (GraalVM) to the native-image classpath to be compiled with gluonfx:compile,

  • merge the configuration json files with others found in the classpath,

  • extract the native libraries into target/gluonfx/$arch-$os/gvm/lib/lib${name}.a, so they can be linked with gluonfx:link,

  • and when targeting Android, extract the Android libraries (.aar) into target/gluonfx/$arch-$os/gvm/android_project/libs. These libraries can be added as regular dependencies for the Android project when creating the apk/aab bundles in the gluonfx:package goal.

7.3.3. Gluon Attach Extended

Gluon Attach Extended is a demo project that shows how to create custom Attach services, using the LogService as a sample, following the previous explanations, but using a different package name for the service.

8. Data Binding

Open Source

Connect is open source, and licensed under the BSD-3 license. Its source code is hosted under Gluon organization in Github.

Gluon Connect is a client-side library that simplifies binding your data from any source and format to JavaFX UI controls. It works by retrieving data from a data source and converting that data from a specific format into JavaFX observable lists and objects. It is designed to allow developers to easily add support for custom data sources and data formats.

By default, Gluon Connect already provides easy-to-use implementations for the most commonly used data sources:

  • File provider

  • REST provider

8.1. File provider

The File provider enables reading from and writing to a file that is located on the file system of the device. You can find the complete sample from our gluon-samples repository on GitHub: https://github.com/gluonhq/gluon-samples. Inside the repository, look for the folder named gluon-connect-file-provider.

8.1.1. Retrieving a list

Let’s assume that we have a file on the local file system with the following JSON content:

languages.json
[
  {"name":"Java","ratings":20.956},
  {"name":"C","ratings":13.223},
  {"name":"C++","ratings":6.698},
  {"name":"C#","ratings":4.481},
  {"name":"Python","ratings":3.789}
]

And the following POJO that will map the JSON objects from the file above to a Java object:

Language.java
public class Language {
    private String name;
    private double ratings;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getRatings() {
        return ratings;
    }

    public void setRatings(double ratings) {
        this.ratings = ratings;
    }
}

We can then assign a list of programming language objects to a JavaFX ListView control with the following code:

// create a FileClient to the specified File
FileClient fileClient = FileClient.create(new File("languages.json"));

// create a JSON converter that converts the nodes from a JSON array into language objects
InputStreamIterableInputConverter<Language> converter = new JsonIterableInputConverter<>(Language.class);

// retrieve a list from a ListDataReader created from the FileClient
GluonObservableList<Language> languages = DataProvider.retrieveList(fileClient.createListDataReader(converter));

8.1.2. Retrieving an object

Retrieving a single object from a file resource looks similar to retrieving a list. Assume that we have a user.json file with the following JSON content:

user.json
{"name":"Duke","subscribed":true}

And the following POJO for mapping the JSON object to a Java object:

User.java
public class User {
    private String name;
    private boolean subscribed;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isSubscribed() {
        return subscribed;
    }

    public void setSubscribed(boolean subscribed) {
        this.subscribed = subscribed;
    }
}

The user object can then be retrieved from the JSON file with the following code:

// create a FileClient to the specified File
FileClient fileClient = FileClient.create(new File("user.json"));

// create a JSON converter that converts a JSON object into a user object
InputStreamInputConverter<User> converter = new JsonInputConverter<>(User.class);

// retrieve an object from an ObjectDataReader created from the FileClient
GluonObservableObject<User> user = DataProvider.retrieveObject(fileClient.createObjectDataReader(converter));

8.1.3. Storing an object

We can use the same file and POJO again to show you how to store an object into a file:

// create an instance of a User to store
User user = new User();
user.setName("Duchess");
user.setSubscribed(false);

// create a FileClient to the specified File
FileClient fileClient = FileClient.create(new File("user.json"));

// create a JSON converter that converts the user object into a JSON object
OutputStreamOutputConverter<User> outputConverter = new JsonOutputConverter<>(User.class);

// store an object with an ObjectDataWriter created from the FileClient
GluonObservableObject<User> gluonUser = DataProvider.storeObject(user, fileClient.createObjectDataWriter(converter));

8.2. REST provider

The REST provider enables reading from and writing to an HTTP URL resource. For demonstrating this we will make use of the public StackExchange API. You can find the complete sample from our gluon-samples repository on GitHub: https://github.com/gluonhq/gluon-samples. Inside the repository, look for the folder named gluon-connect-rest-provider.

8.2.1. Retrieving a list

The URL we are going to use for retrieving a list, is the error REST endpoint. Here is how you would retrieve a list from a URL with Gluon Connect:

// create a RestClient to the specific URL
RestClient restClient = RestClient.create()
        .method("GET")
        .host("https://api.stackexchange.com")
        .path("/2.2/errors");

// retrieve a list from the DataProvider
GluonObservableList<Error> errors = DataProvider.retrieveList(restClient.buildListDataReader(Error.class));

// create a JavaFX ListView and populate it with the retrieved list
ListView<Error> lvErrors = new ListView<>(errors);

As you can see, we didn’t specify any Converter. That is because the RestClient will try to find a suitable Converter based on the Content-Type response header. If the response header is not specified, it assumes that the response will be formatted in JSON. Sometimes, the automatic detection of the Converter fails or you need to use a custom Converter. In that case, you use the second method for creating a ListDataReader where you provide the converter that you want to use.

// create a RestClient to the specific URL
RestClient restClient = RestClient.create()
        .method("GET")
        .host("https://api.stackexchange.com")
        .path("/2.2/errors");

// create a custom converter
InputStreamIterableInputConverter<Error> converter = new ItemsIterableInputConverter<>(Error.class);

// retrieve a list from the DataProvider using the custom converter
GluonObservableList<Error> errors = DataProvider.retrieveList(restClient.createListDataReader(converter));

8.2.2. Retrieving an object

For retrieving a single object, we will use the questions REST endpoint.

// create a RestClient to the specific URL
RestClient restClient = RestClient.create()
        .method("GET")
        .host("https://api.stackexchange.com")
        .path("/2.2/questions/36243147")
        .queryParam("order", "desc")
        .queryParam("sort", "activity")
        .queryParam("site", "stackoverflow");

// retrieve an object from the DataProvider
GluonObservableObject<Question> question = DataProvider.retrieveObject(restClient.createObjectDataReader(Question.class));

// show information on the retrieved object
Label lbTitle = new Label();
question.initializedProperty().addListener((obs, ov, nv) -> {
    if (nv && question.get() != null) {
        lbTitle.textProperty().bind(question.get().titleProperty());
    }
});

8.3. Deep Dive

In the following section we will dive deeper into the concepts of Gluon Connect and what the relation is between these concepts. The two implementations that we talked about above also make use of the same concepts, but they hide them for the user in the internal implementation. If you want to know a bit more about the inner workings of these providers or if you are interested in adding support for a custom DataSource, you should continue reading the following sections.

8.3.1. Concepts

Gluon Connect consists of the following basic concepts:

  • DataSource: specifies where the data is read from and/or written to

  • Converter: converts the read data into objects and vice versa converts objects into data to be written

  • DataProvider: takes care of mapping the data into usable observable lists and objects

DataSource

The DataSource defines where Gluon Connect should read and/or store the data. The DataSource makes use of the standard java.io.InputStream and java.io.OutputStream classes. It is split up into two parts: the InputDataSource that provides an InputStream to read the data from; the OutputDataSource that provides an OutputStream to write the data into. For convenience purposes, there is also an IODataSource that has access to both the InputStream and the OutputStream.

Gluon Connect includes by default two DataSource implementations:

  • FileDataSource: reads and writes data from a File that is accessible from the local system

  • RestDataSource: reads and writes data from an HTTP URL resource

Converter

The Converter is able to convert data from an Object into a specific data format or convert data from a certain data format back into an Object. The Converter is split up into three different parts:

All three interfaces do not specify where the data is coming from. This allows for maximum extensibility. Gluon Connect provides an abstract implementation for these Converters. The InputConverter and IterableInputConverter both have an implementation where the data to convert is taken from an InputStream. The OutputConverter has an analogous implementation that converts the object by writing into an OutputStream.

Gluon Connect provides Converter implementations for JSON and String out of the box.

DataProvider

The DataProvider is the class that will ultimately provide the observable lists or objects that you can use with your JavaFX UI controls. Gluon Connect provides a custom observable list and observable object called GluonObservableList and GluonObservableObject respectively.

The DataProvider itself has four methods:

All these methods will return the GluonObservableList or GluonObservableObject instances immediately. The actual process of retrieving, storing or removing the list or object happens asynchronously in a background thread. For example, when retrieving a list or object you can listen for the initialized property to know when the list or object is fully initialized by the provided reader.

If we for instance look at the retrieveObject method, we see that it has an ObjectDataReader as parameter. The ObjectDataReader is an interface that is able to create a new instance of GluonObservableObject and read an object of a certain type. The source where the object is read from and the format of the data is completely left open to the actual implementing classes. Usually though, you will combine a DataSource with a Converter to implement this functionality. The most basic DataProvider that you can use as an ObjectDataReader is the InputStreamObjectDataReader. This class takes an InputSource as the DataSource and an InputStreamConverter as the Converter. When an instance of InputStreamObjectDataReader is passed into the Dataprovider.retrieveObject method, the object will be retrieved by getting the InputStream from the InputSource and then pass the InputStream to the InputStreamConverter, which will read the data from the InputStream and convert it into the desired object.

8.3.2. Sample

The following sample will use the three concepts by providing a DataSource that reads from a classpath resource, a Converter that converts JSON into objects and a DataProvider that combines the DataSource with the Converter to get access to GluonObservable objects. The complete sample for this can be found in our gluon-samples repository on GitHub: https://github.com/gluonhq/gluon-samples. Inside the repository, look for the folder named gluon-connect-basic-usage.

Retrieving a list

Let’s presume we have the following simple languages.json file that is accessible from the root of the classpath.

/languages.json
[
  {"name":"Java","ratings":20.956},
  {"name":"C","ratings":13.223},
  {"name":"C++","ratings":6.698},
  {"name":"C#","ratings":4.481},
  {"name":"Python","ratings":3.789}
]

And the following POJO that will map the JSON objects from the list above to a Java object:

Language.java
public class Language {
    private String name;
    private double ratings;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getRatings() {
        return ratings;
    }

    public void setRatings(double ratings) {
        this.ratings = ratings;
    }
}

We can then assign a list of programming language objects to a JavaFX ListView control with the following code:

// create a DataSource that loads data from a classpath resource
InputDataSource dataSource = new BasicInputDataSource(Main.class.getResourceAsStream("/languages.json"));

// create a Converter that converts a json array into a list
InputStreamIterableInputConverter<ProgrammingLanguage> converter = new JsonIterableInputConverter<>(ProgrammingLanguage.class);

// create a ListDataReader that will read the data from the DataSource and converts
// it from json into a list of objects
ListDataReader<ProgrammingLanguage> listDataReader = new InputStreamListDataReader<>(dataSource, converter);

// retrieve a list from the DataProvider
GluonObservableList<ProgrammingLanguage> programmingLanguages = DataProvider.retrieveList(listDataReader);

// create a JavaFX ListView and populate it with the retrieved list
ListView<ProgrammingLanguage> lvProgrammingLanguages = new ListView<>(programmingLanguages);
Retrieving an object

Retrieving an object looks somewhat similar to retrieving a list. However, instead of converting a JSON array into a list, we will convert a JSON object directly into a Java object. Below we have the user.json file and the POJO that will hold the information in the JSON object:

/user.json
{"name":"Duke","subscribed":true}
User.java
public class User {
    private StringProperty name = new SimpleStringProperty();
    private BooleanProperty subscribed = new BooleanProperty();

    public String getName() {
        return name.get();
    }

    public void setName(String name) {
        this.name.set(name);
    }

    public StringProperty nameProperty() {
        return name;
    }

    public boolean isSubscribed() {
        return subscribed.get();
    }

    public void setSubscribed(boolean subscribed) {
        this.subscribed.set(subscribed);
    }

    public BooleanProperty subscribedProperty() {
        return subscribed;
    }
}

As you can see, we use JavaFX properties here instead of native Java types. This allows us to bind the properties to a JavaFX UI control, e.g. a Label. To retrieve the object from the JSON file, we use the following code:

// create a DataSource that loads data from a classpath resource
InputDataSource dataSource = new BasicInputDataSource(Main.class.getResourceAsStream("/user.json"));

// create a Converter that converts a json object into a java object
InputStreamInputConverter<User> converter = new JsonInputConverter<>(User.class);

// create an ObjectDataReader that will read the data from the DataSource and converts
// it from json into an object
ObjectDataReader<User> objectDataReader = new InputStreamObjectDataReader<>(dataSource, converter);

// retrieve an object from the DataProvider
GluonObservableObject<User> user = DataProvider.retrieveObject(objectDataReader);

// when the object is initialized, bind its properties to the JavaFX UI controls
Label lbName = new Label();
CheckBox cbSubscribed = new CheckBox("Subscribed?");
user.initializedProperty().addListener((obs, oldValue, newValue) -> {
    if (newValue) {
        lbName.textProperty().bind(user.get().nameProperty());
        cbSubscribed.selectedProperty().bindBidirectional(user.get().subscribedProperty());
    }
});

Gluon CloudLink enables enterprise and mobile developers to easily connect their disparate services and applications together, enabling bi-directional communications between mobile apps, enterprise infrastructure, and cloud systems.

cloudlink and sdks

Gluon CloudLink is a Mobile Back-end as a Service (MBaaS) providing:

  • Data storage, synchronization (to the back-end or across devices) and connectors to other back-ends.

  • User management, including login methods for popular identity providers.

  • Usage analytics

  • Push notifications to Android and iOS devices.

9.1. Application Registration

The first step in enabling Gluon CloudLink in your application is to sign up for a Gluon CloudLink account. If you already have a Gluon CloudLink subscription you can safely skip this part. If not, go to the Gluon CloudLink Product page and choose a subscription that matches your needs. There is also an option to sign up for a 30-day trial account. Enter your billing information or login with your existing account when you have signed up for one of our other products before.

Once you are successfully signed up, a Gluon CloudLink application will be created for you automatically. You will also receive an email that contains a link to access the Gluon Dashboard.

9.1.1. Gluon Dashboard

The Gluon Dashboard is a web application from which you can configure and monitor your Gluon CloudLink application. Point your browser to https://gluon.io and sign in with the same account credentials that you used when subscribing for Gluon CloudLink at gluonhq.com.

login

Select any of the items from the menu on the left to begin configuring your Gluon Application.

Login method

9.1.2. Before you begin

The chapters that follow below will describe all the functional components that are provided by Gluon CloudLink. Each component talks about the required steps on how to set up, configure and use them from the perspective of your enterprise application as well as from the mobile client application. To be able to use Gluon CloudLink you will need to make sure everything is set up correctly.

Server Configuration

Communication between your enterprise application and Gluon CloudLink is done by using the designated Gluon CloudLink Enterprise REST endpoints. Each of these endpoints is described in the appropriate sections below. For your convenience, we also provide a Java Client that calls on these same endpoints. The Java Client currently has two implementations that you can choose from. The Java EE client is most suited when your enterprise application is running inside a Java EE application container. The Spring client is more suited for enterprise applications that are running inside the Spring framework.

JavaDocs for the Gluon CloudLink Enterprise SDK can be found at the following URL:

Content Encoding

All data that is written to Gluon CloudLink by using the enterprise REST endpoints, either directly or by use of the Java Client must be encoded in UTF-8. This is the only encoding that is supported by Gluon CloudLink. If your data you sent looks garbled, please verify that you correctly applied UTF-8 encoding.

Dependencies

The following maven dependencies should be added to your enterprise project to make use of the Gluon CloudLink Enterprise SDK. Note that the Eclipse Yasson dependency is only required when you are running inside a Java EE 7 container. Java EE 8 containers should automatically ship with an implementation of the JSON-Binding API.

Java EE
Maven
<dependencies>
    <dependency>
        <groupId>com.gluonhq</groupId>
        <artifactId>cloudlink-enterprise-sdk-javaee</artifactId>
        <version>1.2.1</version>
    </dependency>
    <!-- only required when running inside a Java EE 7 container -->
    <dependency>
        <groupId>org.eclipse</groupId>
        <artifactId>yasson</artifactId>
        <version>1.0</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>
gradle
repositories {
    mavenCentral()
}

dependencies {
    compile 'com.gluonhq:cloudlink-enterprise-sdk-javaee:1.2.1'

    // only required when running inside a Java EE 7 container
    runtime 'org.eclipse:yasson:1.0'
}
Spring
Maven
<dependencies>
    <dependency>
        <groupId>com.gluonhq</groupId>
        <artifactId>cloudlink-enterprise-sdk-spring</artifactId>
        <version>1.2.1</version>
    </dependency>
</dependencies>
gradle
repositories {
    mavenCentral()
}

dependencies {
    compile 'com.gluonhq:cloudlink-enterprise-sdk-spring:1.2.1'
}
Authentication

The REST endpoints themselves require an HTTP Authorization header that is used to authenticate the requests to Gluon CloudLink. The value of the Authorization header is as follows:

Authorization: Gluon MHwwXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

The SERVER_KEY needs to be replaced with the actual server key of your Gluon CloudLink application and can be found in the Gluon Dashboard, in the Server tab of the Credentials section.

credentials server
JavaEE

When using the Java Client, authentication is done by providing the server key when you create an instance of the Java Client:

Manual instantiation
CloudLinkClientConfig config = new CloudLinkClientConig("MHwwXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
CloudLinkClient client = new CloudLinkClient(config);

Alternatively, when running inside a CDI aware environment, you can inject an instance of the JavaEE client as follows:

Using injection
@Inject
@CloudLinkConfig(serverKey = "MHwwXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
private CloudLinkClient client;
Spring

Authentication with the Spring Client is done in the same way as the JavaEE Client:

Manual instantiation
CloudLinkClientConfig config = new CloudLinkClientConig("MHwwXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
CloudLinkClient client = new CloudLinkClient(config);

When you are using Spring Boot, you can make use of autowiring to inject an instance of the Spring Client. An application property is then required that contains the server key of your Gluon CloudLink Application.

application.properties
gluon.cloudlink.serverKey=MHwwXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Using autowiring
private CloudLinkClient client;

@Autowired
public MyService(CloudLinkClient client) {
    this.client = client;
}
Client Configuration

Gluon Mobile’s CloudLink Client is used for handling the communication between your Mobile application and Gluon CloudLink. It is automatically added as a dependency to the main Gluon Mobile artifact, but can also be added separately.

Maven Configuration

We recommend using Maven as the build tool for building and provisioning a Gluon Mobile application. Maven project can make use of the GluonFX plugin, and the associated pom.xml needs to be configured with the following dependencies and Attach configuration.

<repositories>
    <repository>
        <id>Gluon</id>
        <url>https://nexus.gluonhq.com/nexus/content/repositories/releases</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.gluonhq</groupId>
        <artifactId>charm</artifactId>
        <version>6.2.3</version>
    </dependency>
    <dependency>
        <groupId>com.gluonhq.attach</groupId>
        <artifactId>device</artifactId>
        <version>4.0.19</version>
    </dependency>
    <dependency>
        <groupId>com.gluonhq.attach</groupId>
        <artifactId>push-notifications</artifactId>
        <version>4.0.19</version>
    </dependency>
    <dependency>
        <groupId>com.gluonhq.attach</groupId>
        <artifactId>storage</artifactId>
        <version>4.0.19</version>
    </dependency>
    <dependency>
        <groupId>com.gluonhq.attach</groupId>
        <artifactId>util</artifactId>
        <version>4.0.19</version>
    </dependency>
</dependencies>

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        <target>${gluonfx.target}</target>
        <attachList>
            <list>device</list>
            <list>push-notifications</list>
            <list>storage</list>
        </attachList>
        <mainClass>${main.class}</mainClass>
    </configuration>
</plugin>
Authentication

Gluon Mobile uses the application specific credentials to sign all requests that are made to Gluon CloudLink. This allows Gluon CloudLink to know on behalf of which application the request is initiated.

To apply the client credentials to your Gluon Mobile project, create a JSON configuration file called gluoncloudlink_config.json and save it under the directory src/main/resources. This file will automatically be picked up by Gluon Mobile when needed. Insert the content below, making sure that you replace the values for the applicationKey and applicationSecret with the correct credentials for your application:

{
  "gluonCredentials": {
    "applicationKey": "b916XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "applicationSecret": "9c11XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
  }
}

From the Gluon Dashboard, select the Credentials item from the menu on the left. You will find the key and secret tokens of your Gluon CloudLink application inside the Client tab. You can either manually copy and paste them in the configuration file or you can directly download the configuration file by clicking the download button.

credentials client

9.2. Data Storage

Applications often use data that needs to be stored. Some data is only relevant to a specific instance of the application (i.e. local settings), other data is relevant to all instances of the application (i.e. chat data) and some data is relevant to some instances (i.e. user preferences for a user who has a number of devices).

Gluon CloudLink Client is the component that manages data on the client. It provides an API that allows users to store and synchronize data and provides a number of options for developers to choose from for each data entity:

  • should the data be stored on the device only?

  • should the data be stored in Gluon CloudLink?

  • should changes to data on the device propagate to Gluon CloudLink?

  • in case the data in Gluon CloudLink changes, does the local device copy needs to be synchronized immediately?

Gluon CloudLink Client communicates via an efficient protocol with Gluon CloudLink to achieve the different goals. Gluon CloudLink provides a scalable and persistent data store that is used to store data and to update clients when data changes.

Communication between your Gluon Mobile application and Gluon CloudLink works in two directions:

  • your mobile data is stored in the cloud when you want to, and

  • your mobile data is updated on the device whenever it is updated in the cloud.

In addition, Gluon CloudLink provides the ability to link your Gluon Mobile application with another back-end or cloud infrastructure by configuring one or more Connectors.

Gluon CloudLink makes sure your data is consistent across the different devices. Depending on your preferences, changes on one device propagate immediately to other devices. Or they might only be stored in Gluon CloudLink and the devices will manually request the latest version whenever they want.

DataClient

The DataClient class is the access point to the Data Storage. You can get a reference to a DataClient instance by using the DataClientBuilder:

DataClient dataClient = DataClientBuilder.create().build();
Operation Mode

When working with the DataClient, there are currently three operation modes you can choose from:

  • CLOUD_ONLY: data will only be persisted on and retrieved from the Gluon CloudLink service

  • LOCAL_ONLY: data will only be persisted on and retrieved from the local file system of the device where the application is running on

  • CLOUD_FIRST: data will be persisted on and retrieved from the Gluon CloudLink service as the primary data source, but a local copy will be kept in sync

The default operation mode is CLOUD_FIRST, but can be explicitly specified when creating the DataClient instance:

// specify LOCAL_ONLY operation mode
DataClient dataClient = DataClientBuilder.create()
        .operationMode(OperationMode.LOCAL_ONLY)
        .build();
Authentication

You can configure the DataClient to link data with an authenticated user. The following authentication methods are currently available:

  • Facebook: authenticate users with Facebook Login

  • GitHub: authenticate users with GitHub

  • Google: authenticate users with Google Sign-In

  • Twitter: authenticate users with Twitter

  • Email and Password: authenticate users by letting them sign up and sign in with their personally chosen email address and password

See the documentation on User Management to read more about configuring user authentication.

To enable user authentication on the client, you need to provide a UserClient instance when building the DataClient. The provided UserClient will ensure that an authenticated user is present when the DataClient tries to access underlying data. Authentication is done by presenting the user with an authentication view from which one of the configured login methods can be chosen.

// enable user authentication
UserClient authenticationClient = new UserClient();
DataClient dataClient = DataClientBuilder.create()
        .authenticateWith(authenticationClient)
        .build();

// the next statement will trigger the authentication view
GluonObservableList<Note> notes = DataProvider.retrieveList(dataClient.createListDataReader("notes", Note.class));
Data Storage

Once you have a DataClient reference, you can start storing and retrieving data. There are two different types of data that can be managed in Gluon CloudLink:

  • single objects, and

  • lists of objects

Because these objects and lists are maintained remotely, we will call them remote entities. Every remote entity is identified by a unique string identifier.

Retrieving lists

For example, retrieving a remote list with the identifier notes can be done with the following code:

GluonObservableList<Note> notes = DataProvider.retrieveList(dataClient.createListDataReader("notes", Note.class));
ListView<Note> notesListView = new ListView<>(notes);

In its simplest form the createListDataReader method returns an instance of GluonObservableList without any synchronization flags.

You may have noticed that DataClient doesn’t provide a method for creating a new list. That is because a Gluon CloudLink list always exists. When a list is retrieved the first time, an empty list will automatically be created for you.

Retrieving objects

A remote object works a bit differently than a list, because in contrast to a list, an object does have the notion of existence. This explains why we have three methods for managing remote objects: createObjectDataReader, createObjectDataWriter and createObjectDataRemover.

// store the object
Note note = new Note();
note.setContent("This is the content for the note.");
GluonObservableObject<Note> gluonNote = DataProvider.storeObject(note, dataClient.createObjectDataWriter("a-single-note", Note.class));

// retrieve the object
GluonObservableObject<Note> gluonNote = DataProvider.retrieveObject(dataClient.createObjectDataReader("a-single-note", Note.class));

// remove the object
DataProvider.removeObject(gluonNote, dataClient.createObjectDataRemover());
GluonObservable ConnectState

All the operations on the DataProvider are asynchronous in nature and are executed in a separate background thread. You can listen for changes on the stateProperty of the returned GluonObservable object to monitor the progress of the background operation. To know when your object or list is ready to be used, you can also listen for the initializedProperty instead. Listed below you’ll find a number of common use cases when working with remote entities.

Create a remote object when it does not yet exist

When retrieving an object from Gluon CloudLink, you can detect if this object was already stored previously by using the following pattern:

GluonObservableObject<Note> gluonNote = DataProvider.retrieveObject(dataClient.createObjectDataReader("a-single-note", Note.class));
gluonNote.initializedProperty().addListener((observable, ov, nv) -> {
    if (nv) {
        if (gluonNote.get() == null) {
            // object not yet stored, initiate it now with a new object and store it
            gluonNote.set(new Note("This is some text for the note"));
            dataClient.push(gluonNote);
        } else {
            // object already stored previously
            Note note = gluonNote.get();
            System.out.println("Stored note: " + note.getContent());
        }
    }
});

Initialize an empty list with default objects

When you retrieve a list for the first time, an empty list will be created for you by the Gluon CloudLink service. Sometimes you wish to populate this empty list with default objects. You can do that with the following pattern:

GluonObservableList<Note> gluonNotes = DataProvider.retrieveList(dataClient.createListDataReader("notes", Note.class));
gluonNotes.initializedProperty().addListener((observable, ov, nv) -> {
    if (nv && gluonNotes.isEmpty()) {
        // initialize the list with some default notes
        gluonNotes.addAll(
                new Note("Text for note number 1."),
                new Note("Text for note number 2.")
        );
        dataClient.push(gluonNotes);
    }
});

Detecting failures when working with remote entities

If you notice that data isn’t stored or retrieved correctly, it might be that an exception occurred during the process. You can check the exceptionProperty on the GluonObservable object to see what exactly went wrong.

GluonObservableObject<Note> gluonNote = DataProvider.retrieveObject(dataClient.createObjectDataReader("a-single-note", Note.class));
gluonNote.stateProperty().addListener((observable, oldState, newState) -> {
    if (newState == ConnectState.FAILED) {
        if (gluonNote.getException() != null) {
            gluonNote.getException().printStackTrace();
        }
    }
});
Supported Data Types

The second parameter of the createListDataReader, createObjectDataReader and createObjectDataWriter methods on DataClient specifies the type of data that is stored in the remote entity. Gluon CloudLink supports three different data types: String, Map and custom classes. Inside a Map and for the fields of a custom class, the following field types can be used:

The JavaFX Property types are a requirement when a remote entity is retrieved in combination with the OBJECT_WRITE_THROUGH synchronization flag. See the Data Synchronization section for more information on these flags.

String

The String data type is the most basic of the three supported types. It simply stores and retrieves instances of string.

Map

The Map represents a convenient key/value store in which you can store arbitrary data. It is the most flexible data type, but is less type safe. The keys of the map must be strings, while the value can be any of the supported field types that are listed above. If the map contains a value that is not supported, those values will be ignored when storing or retrieving data.

Custom Class

As a final option, you can define your data structure inside a Custom Class. The DataClient will inspect the provided class for all declared fields of which the type matches any of the supported field types. Note that only non-static fields and non-final primitive fields will be considered. All other field declarations will be ignored when storing or retrieving data.

Data Synchronization

By default, no synchronization flags are configured when calling any of the methods we mentioned above. To enable synchronization, you can pass any of the SyncFlag enum constants to the method. There are four different types of SyncFlag:

  • OBJECT_READ_THROUGH: changes that occur on an object in Gluon CloudLink will be reflected to the fields on the local object

  • OBJECT_WRITE_THROUGH: changes on JavaFX Property fields will be automatically written to the object in Gluon CloudLink

  • LIST_READ_THROUGH: adding and removing objects on a list in Gluon CloudLink will be reflected to the local list

  • LIST_WRITE_THROUGH: adding and removing objects locally will automatically add and remove them to the list in Gluon CloudLink

Note that the OBJECT_READ_THROUGH and LIST_READ_THROUGH flags don’t have any effect when used in combination with the LOCAL_ONLY operation mode.

Also note that the OBJECT_WRITE_THROUGH flag will only work on non-static Observable fields of a Custom Class.

As an example, the code snippet below retrieves an instance of GluonObservableList that is configured with the list read through and list write through synchronization flags. This means that any changes that are done on the client, either adding or removing items, are propagated back to Gluon CloudLink. The reverse is true as well: all changes that occur in Gluon CloudLink will be reflected back to the local list on the device. Changes that occur on the objects inside the list won’t be propagated.

GluonObservableList<Note> notes = DataProvider.retrieveList(dataClient.createListDataReader("notes", Note.class, SyncFlag.LIST_READ_THROUGH, SyncFlag.LIST_WRITE_THROUGH));

9.2.2. Integration with Enterprise and Cloud Systems

One of the features of Gluon CloudLink is to link it with an existing back end infrastructure. There are two main ways for linking Gluon CloudLink with such a back end infrastructure.

  1. Call into the Gluon CloudLink REST endpoints from an enterprise system to retrieve and manage data. You can either directly call the REST endpoints or use an implementation of the Gluon CloudLink Enterprise SDK that best suits your existing enterprise environment.

  2. Use Connectors to let Gluon CloudLink automatically push and/or pull data to and from an enterprise system.

REST endpoints
Get object

Retrieve an object from the data service.

Path Parameters
Name Description

objectIdentifier

The unique identifier of the object to retrieve.

Curl sample
curl -X GET "https://cloud.gluonhq.com/3/data/enterprise/object/sample-object" \
     -H "Authorization: Gluon YOUR_SERVER_KEY"
Java Client
CloudLinkConfig config = new CloudLinkConfig(YOUR_SERVER_KEY);
CloudLinkClient client = new JavaEECloudLinkClient(config);

SampleType sampleObject = client.getObject("sample-object", SampleType.class);
Add object

Add a new object into the data service with the defined payload.

Path Parameters
Name Description

objectIdentifier

The unique identifier of the object to add.

Body

The body of the request must be a UTF-8 encoded JSON string and will be used as the payload for the newly added object.

Curl sample
curl -X POST "https://cloud.gluonhq.com/3/data/enterprise/object/sample-object/add" \
     -H "Authorization: Gluon YOUR_SERVER_KEY" \
     -H "Content-Type: application/json; charset=utf-8" \
     --data '{"text":"someField","number":123}'
Java Client
CloudLinkConfig config = new CloudLinkConfig(YOUR_SERVER_KEY);
CloudLinkClient client = new JavaEECloudLinkClient(config);

SampleType sampleObject = new SampleType("someField", 123);
SampleType storedObject = client.addObject("sample-object", sampleObject);
Update object

Update an existing object in the data service with the defined payload.

Path Parameters
Name Description

objectIdentifier

The unique identifier of the object to update.

Body

The body of the request must be a UTF-8 encoded JSON string and will be used as the payload for the object that is being updated.

Curl sample
curl -X POST "https://cloud.gluonhq.com/3/data/enterprise/object/sample-object/update" \
     -H "Authorization: Gluon YOUR_SERVER_KEY" \
     -H "Content-Type: application/json; charset=utf-8" \
     --data '{"text":"someOtherText","number":321}'
Java Client
CloudLinkConfig config = new CloudLinkConfig(YOUR_SERVER_KEY);
CloudLinkClient client = new JavaEECloudLinkClient(config);

SampleType sampleObject = new SampleType("someField", 123);
SampleType storedObject = client.addObject("sample-object", sampleObject);
storedObject.setText("someOtherText");
storedObject.setNumber(321);
SampleType updateObject = client.updateObject("sample-object", storedObject);
Remove object

Remove an existing object from the data service.

Path Parameters
Name Description

objectIdentifier

The unique identifier of the object to remove.

Curl sample
curl -X POST "https://cloud.gluonhq.com/3/data/enterprise/object/sample-object/remove" \
     -H "Authorization: Gluon YOUR_SERVER_KEY"
Java Client
CloudLinkConfig config = new CloudLinkConfig(YOUR_SERVER_KEY);
CloudLinkClient client = new JavaEECloudLinkClient(config);

client.removeObject("sample-object");
Get list

Retrieve a list of objects from the data service.

Path Parameters
Name Description

listIdentifier

The unique identifier of the list to retrieve.

Curl sample
curl -X GET "https://cloud.gluonhq.com/3/data/enterprise/list/sample-list" \
     -H "Authorization: Gluon YOUR_SERVER_KEY"
Java Client
CloudLinkConfig config = new CloudLinkConfig(YOUR_SERVER_KEY);
CloudLinkClient client = new JavaEECloudLinkClient(config);

List<SampleType> sampleList = client.getList("sample-list", SampleType.class);
Add object to list

Add an object to a list in the data service.

Path Parameters
Name Description

listIdentifier

The unique identifier of the list to add the object into.

objectIdentifier

The unique identifier of the object to add to the list.

Body

The body of the request must be a UTF-8 encoded JSON string and will be used as the payload for the object that is being added into the list.

Curl sample
curl -X POST "https://cloud.gluonhq.com/3/data/enterprise/list/sample-list/add/sample-object-1" \
     -H "Authorization: Gluon YOUR_SERVER_KEY" \
     -H "Content-Type: application/json; charset=utf-8" \
     --data '{"text":"someField","number":123}'
Java Client
CloudLinkConfig config = new CloudLinkConfig(YOUR_SERVER_KEY);
CloudLinkClient client = new JavaEECloudLinkClient(config);

SampleType sampleObject1 = new SampleType("someField", 123);
SampleType addedSampleObject1 = client.addToList("sample-list", "sample-object-1", sampleObject1);
Update object in list

Update an object in a list in the data service.

Path Parameters
Name Description

listIdentifier

The unique identifier of the list where the object to update is stored.

objectIdentifier

The unique identifier of the object to update in the list.

Body

The body of the request must be a UTF-8 encoded JSON string and will be used as the payload for the object that is being updated in the list.

Curl sample
curl -X POST "https://cloud.gluonhq.com/3/data/enterprise/list/sample-list/update/sample-object-1" \
     -H "Authorization: Gluon YOUR_SERVER_KEY" \
     -H "Content-Type: application/json; charset=utf-8" \
     --data '{"text":"someOtherText","number":321}'
Java Client
CloudLinkConfig config = new CloudLinkConfig(YOUR_SERVER_KEY);
CloudLinkClient client = new JavaEECloudLinkClient(config);

SampleType sampleObject1 = new SampleType("someField", 123);
SampleType addedSampleObject1 = client.addToList("sample-list", "sample-object-1", sampleObject1);
addedSampleObject1.setText("someOtherText");
addedSampleObject1.setNumber(321);
SampleType updatedSampleObject1 = client.updateInList("sample-list", "sample-object-1", addedSampleObject1);
Remove object from list

Remove an object from a list in the data service.

Path Parameters
Name Description

listIdentifier

The unique identifier of the list where the object to remove is stored.

objectIdentifier

The unique identifier of the object to remove from the list.

Curl sample
curl -X POST "https://cloud.gluonhq.com/3/data/enterprise/list/sample-list/remove/sample-object-1" \
     -H "Authorization: Gluon YOUR_SERVER_KEY"
Java Client
CloudLinkConfig config = new CloudLinkConfig(YOUR_SERVER_KEY);
CloudLinkClient client = new JavaEECloudLinkClient(config);

client.removeFromList("sample-list", "sample-object-1");
Connectors

A Connector can be used for automatic pushing and pulling of data from Gluon CloudLink to an enterprise back end system.

  • push: data that is updated in Gluon CloudLink is sent through to the enterprise back end

  • pull: data is requested from the enterprise back end by Gluon CloudLink

Different connector implementations are provided by Gluon CloudLink out of the box. Depending on the configured Connector, some extra code will be required on the back end application as well. E.g. when linking to Gluon CloudLink with the REST Connector, a handler must exist on the back end application that listens for HTTP requests that are called by Gluon CloudLink.

Configuring a Connector for a Gluon CloudLink Application is done in the Gluon Dashboard. The following Connectors are available for use within Gluon CloudLink. If you have a specific requirement for a custom Connector, please let us know.

Connector name

Description

REST Connector

This is the most generic connector and allows synchronization data to any enterprise or cloud system that can talk REST.

CloudLink Remote Function

This connector invokes a remote function that is defined in your CloudLink application.

Couchbase Connector

This connector specifically synchronizes data from and to your Couchbase server. Ideally suited if you have an existing Couchbase installation that you want to unlock to mobile devices.

REST Connector

The REST Connector sends and receives data over a network connection using the standard HTTP protocol.

The connector can be set from the Gluon Dashboard, Data Management, Connectors tab.

Data Storage - rest connector
Data Storage - rest connector

When using the REST Connector to link to a back end system, the Gluon CloudLink Application only needs to be configured with the URL where the requests from Gluon CloudLink need to be sent to.

Push endpoints

When a client application requests data to be added, updated or removed from Gluon CloudLink, those requests will be mapped with the REST Controller by making an HTTP request to one of the following six endpoints. Each of them should be implemented on the back end application to be able to handle the request.

Table 4. A new object is added

URL

/object/{objectIdentifier}/add

Method

POST

Request Body

JSON payload of the new object. If the object is a String, the payload will use a key named v.

Description

A new object is added to Gluon CloudLink. The objectIdentifier is the identifier that is passed in from the application client when retrieving or storing the object.

Table 5. An existing object is updated

URL

/object/{objectIdentifier}/update

Method

POST

Request Body

JSON payload of the updated object

Description

An existing object is updated in Gluon CloudLink. The objectIdentifier is the identifier that is passed in from the application client when retrieving or storing the object.

Table 6. An existing object is removed

URL

/object/{objectIdentifier}/remove

Method

POST

Request Body

The request body is empty

Description

An existing object is removed from Gluon CloudLink. The objectIdentifier is the identifier that is passed in from the application client when retrieving or storing the object.

Table 7. A new object is added to a list

URL

/list/{listIdentifier}/add/{objectIdentifier}

Method

POST

Request Body

JSON payload of object that is being added to the list

Description

A new object is added to a list. The objectIdentifier is an identifier that is assigned to the object specific to Gluon CloudLink, i.e. the client application is not aware of this identifier. This is in contrast to the listIdentifier, which is the identifier that is passed in from the application client when retrieving the list.

Table 8. An existing object is updated in a list

URL

/list/{listIdentifier}/update/{objectIdentifier}

Method

POST

Request Body

JSON payload of object that is being updated in the list

Description

An existing object in the list is updated. The objectIdentifier is an identifier that is assigned to the object specific to Gluon CloudLink, i.e. the client application is not aware of this identifier. This is in contrast to the listIdentifier, which is the identifier that is passed in from the application client when retrieving the list.

Table 9. An existing object is removed from a list

URL

/list/{listIdentifier}/remove/{objectIdentifier}

Method

POST

Request Body

The request body is empty

Description

An existing object is removed from a list. The objectIdentifier is an identifier that is assigned to the object specific to Gluon CloudLink, i.e. the client application is not aware of this identifier. This is in contrast to the listIdentifier, which is the identifier that is passed in from the application client when retrieving the list.

Pull endpoints

When a client application requests an object or a list that is not yet known inside Gluon CloudLink, Gluon CloudLink calls one of the following two endpoints on the back end application to retrieve the initial object or list information.

Table 10. An object is requested

URL

/object/{objectIdentifier}

Method

GET

Response Body

JSON payload of the object to retrieve

Description

A new object is being requested from the client application with the specified objectIdentifier.

Table 11. A list is requested

URL

/list/{listIdentifier}

Method

GET

Response Body

JSON payload of the list to retrieve. The payload is a JSON array containing a list of zero or more JSON objects. Each JSON object in the array defines two keys: id that defines the object identifier and payload which is the JSON payload of the object, represented as a JSON string.

Description

A new list is being requested from the client application with the specified listIdentifier.

The CloudLink Remote Function Connector sends data changes by invoking a Remote Function that is configured in your CloudLink application.

Data Storage - cloudlink remote function connector

When a client application requests data to be added, updated or removed from Gluon CloudLink, those requests will be provided with the remote function invocation as a JSON string. For instance, when invoking a REST Remote Function, the payload is provided as the raw body of a POST request.

Pushing Objects
A new object is added
{
    "operation": "objectAdded",
    "objectIdentifier": "01234567-89ab-cdef-0123-456789abcdef",
    "payload" : "{\"firstName\":\"John\",\"lastName\":\"Doe\"}"
}
An existing object is updated
{
    "operation": "objectUpdated",
    "objectIdentifier": "01234567-89ab-cdef-0123-456789abcdef",
    "payload" : "{\"firstName\":\"Jane\",\"lastName\":\"Doe\"}"
}
An existing object is removed
{
    "operation": "objectRemoved",
    "objectIdentifier": "01234567-89ab-cdef-0123-456789abcdef"
}
Pushing Lists
A new object is added to a list
{
    "operation": "itemAddedToList",
    "listIdentifier": "fedcba98-7654-3210-fedc-ba9876543210",
    "objectIdentifier": "01234567-89ab-cdef-0123-456789abcdef",
    "payload" : "{\"firstName\":\"John\",\"lastName\":\"Doe\"}"
}
An existing object is updated in a list
{
    "operation": "itemUpdatedInList",
    "listIdentifier": "fedcba98-7654-3210-fedc-ba9876543210",
    "objectIdentifier": "01234567-89ab-cdef-0123-456789abcdef",
    "payload" : "{\"firstName\":\"Jane\",\"lastName\":\"Doe\"}"
}
An existing object is removed from a list
{
    "operation": "itemRemovedFromList",
    "listIdentifier": "fedcba98-7654-3210-fedc-ba9876543210",
    "objectIdentifier": "01234567-89ab-cdef-0123-456789abcdef"
}
Couchbase Connector

The Couchbase Connector is able to send data to an existing Couchbase Server. The only requirement on the Couchbase Server is an existing bucket that will hold the lists and/or objects from Gluon CloudLink.

couchbase server bucket

When activating the Couchbase Connector inside the Dashboard, you will need to provide the following information to let Gluon CloudLink be able to setup a connection with the Couchbase Server:

  • Nodes: a list of nodes that the Couchbase Client on Gluon CloudLink uses to setup the connection to a Couchbase Cluster. You can specify more than one node, by separating them with a semicolon.

  • Bucket Name: the name of the Couchbase bucket that will hold the lists and/or objects

  • Bucket Password: the password of that Couchbase bucket

Data Storage - couchbase connector
Pushing Objects

Objects are stored in the Couchbase bucket using a key named objects/{objectIdentifier}, where objectIdentifier is the identifier that is passed in by the client application when storing or retrieving the object.

The document itself will be a JSON document that represents the JSON payload of the object. An example of such an object can be seen below:

objects/notes-settings
{
  "fontSize": 10,
  "sortingId": 2,
  "ascending": true,
  "showDate": false
}
Pushing Lists

Lists are also stored as a document with a key named lists/{listIdentifier}, where listIdentifier is the identifier that is passed in by the client application when retrieving the list.

For each object in the list, the document will contain a key that matches the identifier of the object. The value that is mapped to that key is a JSON document that represents the JSON payload of the object. Below is an example of a list that contains two objects:

lists/notes
{
  "af52f4c6-a64b-4823-b9fb-3cbef79d7577": {
    "creationDate": 1463062055,
    "title": "new note",
    "text": "sample 2"
  },
  "f880774a-20e9-11e6-b67b-9e71128cae77": {
    "creationDate": 1463054952,
    "title": "another note",
    "text": "and also another sample text"
  }
}
Pulling Data

Gluon CloudLink can also pull data from the same Couchbase Server when a list and/or object is retrieved that is not yet known within the Gluon CloudLink data store. It will try to retrieve a list or object from the configured bucket, by using the same identifiers as described in the push section above: lists/{listIdentifier} for lists and objects/{objectIdentifier} for objects. The format of the documents stored inside the Couchbase bucket must also follow the same format as described in the previous section.

9.3. Remote Functions

Almost all mobile applications that exist today interact with a back end infrastructure for managing their data. In theory, an application can directly make requests to these back ends, but there are a number of drawbacks for doing this:

  • Mobile devices are not always connected to the internet. It takes lots of boilerplate code to check connectivity and handle rescheduling of requests when connectivity is restored.

  • Applications on mobile devices have specific lifecycles and need to behave according to specific policies. Some resources are conditionally available. The battery might be low, the application might be running in the background, the device is connected with a paid cellular network, etc. Depending on those conditions, an application must behave differently.

  • Mobile devices have less resources than the regular server, and those resources need to shared with other applications.

CloudLink provides Remote Functions to give the application a reliable and secure way for linking with existing back end systems.

9.3.1. Managing Remote Functions

Managing remote functions is done in the API Management section of the Gluon Dashboard. Each remote function is uniquely identified by a name. This name will be used in the client application when it makes a call to the defined remote function.

Sign in to the Gluon Dashboard and navigate to API Management. You will find the following sections:

  • Functions: this is where the remote functions are configured

  • Authentication: in here different authentication schemes can be created that are used when authentication is required by a remote function

  • Call Log: logging information for calls that are made to a remote function

Api Management - overview
Remote Function Types

We currently support the following types to choose from when creating a new remote function:

  • HTTP Request: executes an HTTP request based on the configured parameters

  • Amazon AWS Lambda: calls an Amazon AWS Lambda function

  • Azure Function: calls a Microsoft Azure Function

  • Fn Project Function: invoke a function on Fn

  • Gluon Function: invoke a function that is running on Gluon CloudLink

Create Remote Functions - HTTP Request

Click the + button to add a new remote function and choose HTTP Request. A dialog will be shown where the following components can be configured:

Api Management - remote function http request
Function Name

The function name is a unique identifier for the remote function. The name is used in the client application when a call needs to be triggered to the remote function.

Enable Mock Response

Mock responses can be enabled for testing purposes. When mocking is enabled, a call to the remote function will not create a request to the actual configured endpoint, but respond directly with the data that is defined for the mock response.

Method

The method defines what HTTP method to use when creating the request. The following methods are supported: GET, POST, PUT and DELETE.

Endpoint

The endpoint is the URI to use when creating the request.

Read Timeout

Specify the timeout, in milliseconds, for reading from the remote function endpoint. If the value is zero, the default timeout will be used.

Connect Timeout

Specify the timeout, in milliseconds, for connecting to the remote function endpoint. If the value is zero, the default timeout will be used.

Authentication Method

Specifies the authentication method that must be used when executing the request to the remote function. The authentication method can be selected from a list of authentication methods that are configured in the Authentication section.

Body Type

The body type can be specified when the POST or PUT method is selected. The body type defines what kind of data will be sent to the remote function. The following types are supported:

  • none: the request will be sent without a request body

  • x-www-form-urlencoded: for sending form url encoded requests

  • form-data: for sending form data multipart requests

  • raw: for sending raw text, i.e a json string

When the raw body type is chosen, two extra fields will be available to specify the data and the media type of the raw body content.

Caching Enabled

If caching is enabled, CloudLink caches each successful response from invocations to the remote function for one hour. CloudLink caching rules are based on standard HTTP caching mechanisms. In closer detail, two HTTP response headers are currently inspected:

  • ETag: if an entity tag is provided, it will be used in any subsequent request to the configured endpoint for cache validation.

  • Cache-Control: if cache control contains the words private or no-store, the response is not cached. If it contains public (default value) or a max-age value, the response is cached for the specified duration.

Both HTTP response headers can be combined to improve the caching mechanism.

Create Remote Functions - Amazon AWS Lambda

Before you can create a remote function for an AWS Lambda function, you need to add customer credentials that point to a valid Amazon AWS access key. Navigate to the Credentials section in Gluon Dashboard and choose the Customer tab. Add new credentials by clicking the + button and choose AWS Access Key. Enter the required information.

Credentials - aws access key

Navigate back to the API Management section and click the + button to add a new remote function while choosing Amazon AWS Lambda. A dialog will be shown where the following components can be configured:

Api Management - remote function aws lambda
Function Name

The function name is a unique identifier for the remote function. The name is used in the client application when a call needs to be triggered to the remote function.

Enable Mock Response

Mock responses can be enabled for testing purposes. When mocking is enabled, a call to the remote function will not create a request to the actual configured endpoint, but respond directly with the data that is defined for the mock response.

AWS Credentials

The AWS Access Key to use when listing the available AWS Lambda functions.

AWS Region

The AWS region where the AWS Lambda functions must be listed from.

AWS Lambda Function

The actual AWS Lambda function to link with this remote function.

AWS Lambda Version

An optional specific version of the AWS Lambda function that must be executed. Leaving this empty will use the default $LATEST version of the chosen AWS Lambda function.

Payload Type

An optional payload that should be sent along when executing the AWS Lambda function. The following types are supported:

  • none: no payload will be sent with the call

  • byte array: sends an array of raw bytes encoded as a Base64 string, useful when sending binary data from the client

  • string: useful for sending plain text

Payload Variable Name

When the selected payload type is string or byte array, you can specify the variable name of the payload. The payload is passed down to the AWS Lambda function as a JSON object containing the variable name as a key. When the payload type is string, the value mapped with the key will be the JSON value that is loaded from the Payload string that can be specified in the text area below. If the payload type is byte array, then the JSON value will be a Base64 encoded string of the passed in array of bytes.

Payload

When the string payload type is chosen, an extra text area will be available to specify the data for the string content. The resulting data string must be valid JSON. If this is not the case, the request to the remote function will be aborted with a Bad Request status.

Output Media Type

Defines the expected media type of the response from the AWS Lambda function. If left empty, the default value of text/plain will be used instead.

Read Timeout

Specify the timeout, in milliseconds, for reading from the remote function endpoint. If the value is zero, the default timeout will be used.

Connect Timeout

Specify the timeout, in milliseconds, for connecting to the remote function endpoint. If the value is zero, the default timeout will be used.

Create Remote Functions - Azure Function

Configure a remote function that can invoke a Microsoft Azure function with an http trigger. Click the + button to add a new remote function and choose Azure Function. A dialog will be shown in which the following components can be configured:

Api Management - remote function azure
Function Name

The function name is a unique identifier for the remote function. The name is used in the client application when a call needs to be triggered to the remote function.

Enable Mock Response

Mock responses can be enabled for testing purposes. When mocking is enabled, a call to the remote function will not create a request to the actual configured endpoint, but respond directly with the data that is defined for the mock response.

Method

The method defines what HTTP method to use when creating the request. The following methods are supported: GET, POST, PUT and DELETE.

Function URL

The Function URL that points to the Azure function.

API Key

Define the Function Key to use for authorizing the invocation to the Azure function. The key is passed down to the function using the x-functions-key HTTP header. Make sure that you copy the value of the Function Key and not its name. Azure functions using Anonymous authorization can leave this field empty.

Read Timeout

Specify the timeout, in milliseconds, for reading from the remote function endpoint. If the value is zero, the default timeout will be used.

Connect Timeout

Specify the timeout, in milliseconds, for connecting to the remote function endpoint. If the value is zero, the default timeout will be used.

Body Type

The body type can be specified when the POST or PUT method is selected. The body type defines what kind of data will be sent to the remote function. The following types are supported:

  • none: the request will be sent without a request body

  • x-www-form-urlencoded: for sending form url encoded requests

  • raw: for sending raw text, i.e a json string

When the raw body type is chosen, two extra fields will be available to specify the data and the media type of the raw body content.

Create Remote Functions - Fn Project Function

These are remote functions that are configured to run on the Fn Project platform. Your Fn function must be available from a public registry on Docker Hub. Click the + button to add a new remote function and choose Fn Project Function. A dialog will be shown in which the following components can be configured:

Api Management - remote function fn project function
Function Name

The function name is a unique identifier for the remote function. The name is used in the client application when a call needs to be triggered to the remote function.

Enable Mock Response

Mock responses can be enabled for testing purposes. When mocking is enabled, a call to the remote function will not create a request to the actual configured endpoint, but respond directly with the data that is defined for the mock response.

Docker Registry

The name of the docker image as it has been pushed to Docker Hub. You can optionally specify a tag for the image, for instance: gluonhq/helloworld:0.0.2.

Enable Input

When input is enabled, two extra fields will be available to specify the data and the media type of the input. The content of the text area will be passed down to the Fn Function during invocation.

Timeout

Specify the timeout, in seconds, for executing the remote function on the Fn Project platform. If the value is zero, the default timeout will be used.

Create Remote Functions - Gluon Function

Gluon Functions allow you to run Java functions inside a serverless platform that is managed by Gluon CloudLink itself. All you need is a zip file that bundles all the runtime jar files that are needed for running the function. The Gluon IDE plugins assist you with creating and deploying of your Gluon Remote Functions.

Click the + button to add a new remote function and choose Gluon Function. A dialog will be shown in which the following components can be configured:

Api Management - remote function gluon function
Function Name

The function name is a unique identifier for the remote function. The name is used in the client application when a call needs to be triggered to the remote function.

Enable Mock Response

Mock responses can be enabled for testing purposes. When mocking is enabled, a call to the remote function will not create a request to the actual configured endpoint, but respond directly with the data that is defined for the mock response.

Entrypoint

The entrypoint defines what method should be invoked when the Gluon Function is triggered. The syntax consists of the fully qualified class name and the method name to be invoked, separated with a double colon, i.e.: com.gluonhq.sample.function.GluonFunction::methodName

Java Runtime

Specify the Java Runtime environment that must be used for running the Gluon Function. You can choose between Java 8 and Java 9.

Memory

Define the maximum amount of memory that should be provided when running the Gluon Function. You can choose a value between 128MB and 1GB with intervals of 128MB.

Bundle

The bundle contains the actual classes that are needed for executing the Gluon Function. The bundle is a zip file containing one or more jar files that will be added to the classpath of the Gluon Function.

Read Timeout

Specify the timeout, in milliseconds, for reading from the remote function endpoint. If the value is zero, the default timeout will be used.

Connect Timeout

Specify the timeout, in milliseconds, for connecting to the remote function endpoint. If the value is zero, the default timeout will be used.

Parameters

Each remote function can be configured with additional parameters. Each parameter consists of a type, a name and optional default and test values. The name is used by the client application to define the value for the parameter that will be passed on when building the request for the remote function. In the image below, a query parameter is configured with the name tagged.

Double clicking inside the grid will activate the edit view for the selected function parameter.

Api Management - parameters

Four different types of parameters are currently supported.

Query

A query parameter will be sent as part of the query string. The query string is the part of the URI that comes after the question mark.

Note: only supported for remote functions of type HTTP Request

Form

A form parameter can be chosen for remote functions that are configured with the POST method. Form parameters are sent as form url encoded parameters in the body of the request.

Note: only supported for remote functions of type HTTP Request

Header

A header parameter can be used to send custom HTTP request headers when building the request for the remote function.

Note: only supported for remote functions of type HTTP Request

Variable

Variable parameters can be used to add custom variables to certain fields of a remote function. Remote functions of type HTTP Request can have custom variables in the Endpoint and Raw Data fields; Remote functions of type Amazon AWS Lambda can have custom variables in the Payload field. Also, each custom variable that is added to an Amazon AWS Lambda Remote Function will be passed along with the JSON payload as well.

For example, the following URI endpoint contains a single variable called userIdentifier: https://foo.bar/user/$userIdentifier. The variable will be replaced with the value that was passed on by the client application, before the actual request is executed.

9.3.2. Authentication

The endpoint of a remote function sometimes requires that the request is authenticated. The Authentication section provides three different authentication mechanisms that can be used together with a remote function.

Api Management - authentication
Basic Authentication

This will add a basic Authorization HTTP header when creating the request to the remote function. The username and password are both required.

OAuth 1.0 with Consumer Credentials

This authentication method will sign each request with the provided consumer key and secret using the HMAC-SHA1 signature method. See the OAuth 1.0 documentation on signing requests for more details: https://oauth.net/core/1.0a/#signing_process.

A token key and secret can be provided as well when necessary, but can be left empty if the endpoint only requires that the request must be signed with consumer credentials.

OAuth 2.0 with Password Credentials Grant

This authentication method will apply the Resource Owner Password Credentials authorization grant as defined in the OAuth 2.0 specificiation: https://tools.ietf.org/html/rfc6749#section-4.3.

When making a request to the defined endpoint of the remote function, it will first try to get an access token using the configuration details of the authentication method. The access token will then be passed along with the actual request to the endpoint of the remote function.

9.3.3. Calling Remote Functions

Testing

Each remote function can be tested from within Gluon Dashboard to ensure the configuration is valid. Each configured parameter has an optional test value that will be used when testing the remote function. When no test value is provided, the default value will be used instead.

When testing the remote function, the response of the endpoint will be printed so it can be verified against the expected value.

Call Log

Useful information from each call that is being invoked by a remote function is stored in the Call Log and can be accessed in Gluon Dashboard. Each request records the response code, the request and response timestamps and the body of the request and response. The body is capped at 8k.

Api Management - call log

For calling a remote function from the client application we use the RemoteFunctionBuilder. The RemoteFunctionBuilder can generate three different implementations of a RemoteFunction, each handling the response of the call to the remote function in their own way:

  • RemoteFunctionObject: the response is converted and wrapped into a GluonObservableObject

  • RemoteFunctionList: the response is converted into a list of objects, contained in a GluonObservableList

  • RemoteFunctionChunkedList: the response of the function is a continuous stream of chunks, where each chunk is converted and added to a GluonObservableList

Basic Remote Functions

To start calling remote functions, we first need to build an instance by using the RemoteFunctionBuilder builder class. Each built RemoteFunction instance can then be triggered by calling the call method.

// create an instance of remote function
RemoteFunctionList remoteFunction = RemoteFunctionBuilder.create("myFunction")
        .param("myParameter", "parameterValue")
        .list();

// call the remote function to fetch an observable list
GluonObservableList<MyResponse> responses = remoteFunction.call(MyResponse.class);
Local Caching

Every response that is returned by a call to a remote function will by default be cached locally in the private storage of the device. The next time the remote function is called, it will first load and return the cached data before making the actual call to the remote function in Gluon CloudLink. This allows the application to already present the user with data from the last time the remote function was called. When the response from the call to the actual remote function in Gluon CloudLink is completed, it will overwrite the cached data with the data from the new response.

Caching can be explicitly disabled when building the remote function:

// create an instance of remote function without caching locally
RemoteFunctionObject remoteFunction = RemoteFunctionBuilder.create("anotherFunction")
        .cachingEnabled(false)
        .object();

The cached data itself can also be cleared by using the clearCache() method:

// clear the locally cached data
remoteFunction.clearCache();

// the GluonObservableObject instance will no longer receive
// the cached data as it was cleared
GluonObservableObject<Sample> sample = remoteFunction.call(Sample.class);
Chunked Remote Functions

By default, Gluon CloudLink will close the call to the endpoint that is configured for a remote function after 60 seconds. However, the connection will be kept open when the remote function uses chunked transfer encoding. This is handled automatically, when the remote function specifies the Transfer-Encoding response header with the value chunked in its response. In other words, there is nothing special that needs to be configured in your Remote Function definition on Gluon Dashboard.

At the client side, you do need to use a different implementation of RemoteFunction that is able to handle chunked encoding: RemoteFunctionChunkedList.

// create an instance of a chunked remote function
RemoteFunctionChunkedList chunkedFunction = RemoteFunctionBuilder.create("chunkedFunction")
        .param("myParameter", "parameterValue")
        .chunkedList();

// call the remote function to fetch an observable list. Gluon CloudLink will add a
// new item to this list, for each chunk it receives from the remote function
GluonObservableList<MyChunk> responses = chunkedFunction.call(MyChunk.class);
Remote Functions with binary data

Writing binary data with a remote function can be done by defining the remote function in Gluon Dashboard with the raw body type. In the client application, a byte array is provided as the raw body when building the remote function.

// retrieve bytes from a classpath resource
byte[] rawBody = {};
try (ByteArrayOutputStream os = new ByteArrayOutputStream();
     InputStream is = Main.class.getResourceAsStream("/sample.wav")) {
    byte[] bytes = new byte[4096];
    int read;
    while ((read = is.read(bytes)) != -1) {
        os.write(bytes, 0, read);
    }
    rawBody = os.toByteArray();
} catch (IOException e) {
    // handle exception
}

// create an instance of remote function with raw bytes
RemoteFunctionObject playSoundFunction = RemoteFunctionBuilder.create("playSound")
        .rawBody(rawBody)
        .object();

// call the remote function passing on the raw bytes
GluonObservableObject<Void> response = playSoundFunction.call(Void.class);
response.stateProperty().addListener((obs, ov, nv) -> {
    if (nv == ConnectState.SUCCEEDED) {
        System.out.println("Sound uploaded successfully.");
    }
});

9.3.4. End to End Guide

You can find end to end guides on working with different cloud servers with Gluon Cloudlink below:

9.4. User Management

The User Management service enables user authentication in your Gluon Mobile application. It supports email and password based authentication and signing in with most popular identity providers like Facebook, Google and more. Each of these authentication types is called a Login Method in Gluon CloudLink.

Here is an overview of the login methods that are currently supported by Gluon CloudLink:

  • Facebook Login

  • Google Sign-In

  • Twitter

  • GitHub

  • Email and Password

9.4.1. Enabling Login Methods

Enabling the login methods that should be available for your application can be done from the Gluon Dashboard. Navigate to the User Management link, and select the Login Methods tab. From here you can add and configure one or more login methods.

View configured login methods

The login methods for identity providers all need a key and a secret from an application that is created on the respective identity provider. We provide a step-by-step guide to creating and configuring an application for each of the supported identity providers.

9.4.2. Applying Login Methods

UserClient

The UserClient class is the access point to the User Management service. It contains various methods for managing the authentication process. When a new instance is constructed, it will load the Login Methods that are enabled for the Gluon Mobile application and present them to the user when the authentication process is started.

Initiate authentication

A typical workflow to authenticate a user would be coded as follows:

UserClient userClient = new UserClient();
userClient.authenticate(user -> {
    System.out.println("User authenticated successfully: " + user.getName());
});

When no authenticated user exists yet, the authentication process will be started. This is handled by taking the user to the implementation of the AuthenticationView. The default AuthenticationView implementation looks like this:

App Sign in Methods

The user can select one of the presented login methods which will start the authentication flow for the selected login method. When a user was successfully authenticated, or when an authenticated user was already present, the provided consumer in the authenticate method will be called, passing in the authenticated user.

Handling failed or aborted authentication

In case the user aborted the authentication process or when an unexpected error occurred, you can use the authenticate variant which takes an extra failure consumer.

userClient.authenticate(user -> System.out.println("User authenticated successfully!"),
    failure -> System.out.println("Authentication failed or aborted: " + failure));
Signing out

Once a user is successfully authenticated, that user will not need to authenticate again the next time the application is started. To be able to restart the authentication process , you will first need to call the signOut method.

User

The authenticated user can be retrieved from the UserClient by calling the getAuthenticatedUser method. This returns an instance of User that has the following fields available:

  • key: the unique identifier of the user within the entire application

  • name: the full name of the user

  • nick: an optional nick name for the user

  • picture: an optional URL to the profile picture of the user

  • networkId: the identifier of the user at the identity provider that was used for authentication

Data Authentication

As mentioned in the Data Storage section, you can also use a UserClient to make sure that only authenticated users have access to data that is loaded by a DataClient. To enable this, you need to pass in an instance of UserClient when building the DataClient:

UserClient userClient = new UserClient();
DataClient dataClient = DataClientBuilder.create()
        .authenticateWith(userClient)
        .operationMode(OperationMode.CLOUD_FIRST)
        .build();

The first time that DataClient instance is used to access data, the authentication process will be initiated when an authenticated user was not yet present on the provided UserClient instance.

9.5. Media Management

The Media Management service is a central place to manage media resources that are used inside your mobile application. We distinguish the following different media types:

  • Media: refers to images, video and audio that can be used in the mobile application

  • Resource Bundles: refers to i18n resource bundle property files for translating application copy

9.5.1. Media

Each media resource is defined by a unique name. The client application will request the media resource by specifying that name. Each media is further made up of one or more media variants. The variant contains the actual media file, together with metadata to define for which platform the media should be made available. That way, it is for instance possible to define a different version of a media resource for Android and iOS.

Uploading Media

From the Gluon Dashboard, selecting the Media Management link will present you with a view that is divided into two grids. The top grid holds the media, while the bottom grid will show the media variants that are linked with the selected media from the top grid.

Add a new media resource by clicking the plus button from the top grid. This will create the media container as well as the first associated media variant. The following fields can be defined in the form dialog:

Media Name

This defines the unique name for the media resource. The client application will use this name to get a reference to the media resource.

Platform

Associates the media variant with a specific platform. When the client application requests a media resource and a specific variant exists that matches the platform where the application is running at, that media variant will be returned. Leave the platform empty to define the fallback resource that will be returned when no variant was found for a specific platform and/or platform version.

Platform Version

You can further specialize the media variant by defining a matching platform version. This media resource will only be returned when both the platform and the version of the platform where the application is running on match with the specified values.

Media File

This is the ultimate resource file that is linked with the media variant. It’s also this file that is returned to the client application in the form of an InputStream.

Loading Media Resources on the Mobile Client

The MediaClient is the class that you need to load media resources on the mobile application. You only need to know the name of the media resource to load and call one of the two available functions:

  • loadMedia: loads an arbitrary media resource and returns it’s data as an InputStream

  • loadImage: loads an image media resource into a JavaFX Image

The following code snippet shows an example of how to show a media resource into a Glisten View:

public void initialize() {
    MediaClient mediaClient = new MediaClient();
    Image image = mediaClient.loadImage("backgroundImage");

    StackPane pane = new StackPane();
    pane.getChildren().add(image);

    setCenter(pane);
}

9.5.2. Resource Bundles

Resource Bundles are the standard way for providing i18n functionality in Java. Resources bundles are typically provided by one properties file or class for each supported language. These properties files or classes are then shipped together with your application package. With Gluon CloudLink we add the possibility to provide the resource bundles as an internet resource.

The resource bundle consists of a resource file for each supported locale. When adding a new resource bundle, you specify the locale that defines the associated language, country, script, etc. When the client application requests the resource bundle, it also passes down a locale so that Gluon CloudLink can return the resource bundle that matches the given locale.

Uploading Resource Bundles

Resource Bundles can be uploaded from the Gluon Dashboard. Navigate to Media Management and choose the Resource Bundles tab. Click the + button to add a new resource bundle. The resource bundles are grouped by their bundle name. The most common scenario is to create a resource bundle for each view in your mobile application.

Note: it is best practice to always provide a version of the resource bundle with an empty locale. This way, when no matching resource bundle could be found for a given locale, the resource bundle with the empty locale will be used as a fallback.

Loading Resource Bundles on the Mobile Client

We call the method loadResourceBundle on an instance of MediaClient to load a resource bundle from Gluon CloudLink. You can use the returned Resource Bundle to load it into your View.

public void initialize() {
    MediaClient mediaClient = new MediaClient();
    ResourceBundle resourceBundle = mediaClient.loadResourceBundle("com.gluonhq.sample.BasicView",
            Locale.getDefault());

    Label label = new Label(resourceBundle.getString("greeting"));

    StackPane pane = new StackPane();
    pane.getChildren().add(label);

    setCenter(pane);
}

9.6. Usage Analytics

The Usage Analytics service is responsible for gathering statistics on the devices that are running your Gluon Mobile application. That information can then be visualised and analysed from within the Gluon Dashboard web application.

The data that is being gathered contains information about the requests that the Gluon Mobile application makes to the Gluon CloudLink services. It also contains general information about the device itself, like the platform, the model, the version of the application, etc.

9.6.1. Applying Usage Analytics

UsageClient

To enable usage analytics in your Gluon Mobile application, you will need to call the enable method on the UsageClient. The method can be called at any time, but ideally it should be called as soon as the application is launched.

UsageClient usageClient = new UsageClient();
usageClient.enable();

This will trigger a call to the Gluon CloudLink Usage service that stores the general device information. The trigger will only be sent the first time, so any subsequent calls to the enable method will do nothing.

pom.xml

Don’t forget to add the Attach device plugin in the pom.xml file of your Gluon Mobile project.

...
<dependency>
    <groupId>com.gluonhq.attach</groupId>
    <artifactId>device</artifactId>
    <version>4.0.19</version>
</dependency>
...
<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>gluonfx-maven-plugin</artifactId>
    <version>1.0.22</version>
    <configuration>
        ...
        <attachList>
         <list>device</list>
        ...
        </attachList>
        <mainClass>${main.class}</mainClass>
    </configuration>
</plugin>

9.6.2. Gluon Dashboard

The Gluon Dashboard can be used to inspect the usage information that is being logged by the devices that are running your Gluon Mobile application. When the UsageClient is enabled as shown in the previous section, there is nothing else to configure in the Gluon Dashboard. By default, the data that is shown is gathered from the devices that were active during the past two weeks.

Usage Analytics Graph

9.7. Push Notifications

The Push Notifications service enables sending push notifications to the devices that installed your Gluon Mobile application. A push notification is a notification message that can be sent to the device, even when the application is not running. This can be used to unobtrusively notify a user that an application specific event has triggered.

9.7.1. Enabling Push Notifications

Gluon CloudLink uses Firebase Cloud Messaging (FCM) for handling push notifications over to Android devices and Apple Push Notification service (APNs) to get push notifications delivered to iOS devices. Both FCM and APNs require credentials for accessing their services.

9.7.2. Firebase Cloud Messaging

To be able to use push notifications in your Gluon Mobile application on android, you need the correct credentials for accessing Google’s FCM services.

Sign in to the Firebase Console with your Google Account and create a new project, or choose one of your existing projects to enable FCM for that project.

FCM - create or choose an app

Fill in the project name, click Continue.

FCM - set project name

The next step will allow enabling Google Analytics, which is optional. Press continue and wait until the project has been created.

FCM - project created

Press continue, and you will get to the project’s console.

FCM - project console

Let’s add Firebase support to an Android app, by selecting the Android icon. Fill in the package name of the android application. The package name should match the name of the package that is configured in Android Manifest of your Gluon Mobile application.

FCM - Android app

Click Register app to continue and then download the configuration file, it should be added to the src/main/resources folder of your project.

FCM - Configuration file

Press next twice (we don’t need to add the Firebase SDK, as Gluon Attach takes care of it), and finally press Continue to the console.

FCM - Continue

Back to the project’s console, in here you can configure which Google services should be enabled in the Google Application.

Select the settings icon to the right of Project Overview, and choose the Cloud Messaging service.

FCM - Cloud messaging credentials

A Server API Key and Sender ID are generated for the Android app.

Gluon Dashboard

Browse to the Gluon Dashboard, select the Push Notifications link and navigate to the Configuration tab. Paste the Server API Key into the textfield in the Android section at the top. Don’t forget to click the Save button to apply your changes.

GCM - enter credentials in Gluon Dashboard

9.7.3. iOS Push Notifications

There are two main steps required to enable push notifications on iOS devices: First, get a valid p12 certificate, that will be required by the Dashboard, and second, get a valid provisioning profile to sign the app that will receive the notifications.

In both cases you need a valid account in the Apple Developer portal. More information about iOS notifications can be found here.

Certificate p12

Go to the Apple Developer portal, sign in with your credentials, and create a certificate for your app following these steps:

  • Select Certificates, Identifiers and Profiles

  • Under Identifiers → App IDs, click the + button to register your app.

    • Under App ID Description: provide a valid name, i.e: Push Notes App.

    • Under App ID Prefix: you should see your team ID.

    • Under Explicit App ID, Bundle ID: you have to put the exact same bundle of your app as the one you’ll find in the info.plist file, i.e: com.gluonhq.pushnotes.PushNotes.

App ID Description and Bundle ID
  • Under App Services, select push notifications and click Continue.

App Services
  • Confirm you App ID: make sure your App ID information is correct, and click the Register button.

Confirm App ID
  • Registration complete. Click Done.

  • Under Identifiers → App IDs, now the created app ID it will be listed. Click on it, and at the end of the expanded info click on the Edit button.

Edit App Configuration
  • Go to the Push Notifications section and click on Create Certificate for develpment, production or both.

Create Certificate for Push Notifications
  • Follow the instructions on how to generate a Certificate Signing Request (CSR) file on your Mac.

    • Open Keychain Access app and select Keychain Access > Certificate Assistant > Request a Certificate from a Certificate Authority.

    • Add your email, name, select Save to disk, click continue.

    • Save the file, i.e. CSR_PushNotes.certSigningRequest.

  • Upload the CSR file

    • At the Developer center again, click on Continue, click on the Choose File…​ button, and submit your file.

Submit CSR file
  • Download the certificate aps_development.cer and double click on it to install the certificate in Keychain Access.

Download cer file
  • Once Keychain Access shows the certificate, expand it, select it and your name, and right click, selecting Export 2 items, and save the file on your disk, i.e. PushNotes_Certificate.p12 providing a password for it.

Export Items in Keychain Access
  • Important note: This file and the password will be required later on the push notifications configuration tab of the Gluon Dashboard.

Add p12 to Dashboard
Provisioning profile

One last step is required to be able to sign your app: get a provisioning profile for it.

  • Back again to the Apple Developer Center, under Provisioning Profiles (Development or Distribution), click on the + button to add a new profile.

    • Select iOS App Development or App Store, and press Continue.

    • Select the App ID from the combobox, and press Continue.

Select App ID
  • Select the certificates you wish to include in this provisioning profile, and press Continue. These will be used later (iosSignIdentity).

  • Select the devices you wish to include in this provisioning profile, and press Continue.

  • Give a name to the profile, and press Continue

  • The provisioning profile is ready to be downloaded.

Download Provisioning Profile
Gluon Dashboard

When you’ve managed to prepare your application for both FCM and APNs, you can configure your Gluon Mobile application in the Gluon Dashboard. Select the Push Notifications link from the menu, go the Configuration tab, where you can enter the FCM Server API key and upload your APNs certificate.

Push Notifications Configuration

With the proper certificates, the Push Notifications tab can be used to send a push notification. Clicking on the + button will pop up a dialog from which you can enter the details of the notification.

Push Notifications Dialog

The checkbox labelled invisible can be selected to send silent push notifications to the user, without a visible notification. The Runtime Args service will be able to process it and execute certain action.

9.7.4. Using Push Notifications from Enterprise

REST endpoint
Send Push Notification

Send a new push notification to the defined target devices.

Form Parameters
Name Type Description Default

body

string

the body of the push notification

deliveryDate

long

Note: this parameter is currently ignored

0

expirationAmount

integer

the amount used in conjunction with the expiration type

4

expirationType

DAYS, HOURS, MINUTES, WEEKS

the type of the expiration amount

WEEKS

identifier

string

a custom identifier to be sent along with the push notification

invisible

boolean

specify true to send a silent push notification

false

priority

HIGH, NORMAL

The priority for the push notification.

HIGH

targetDeviceToken

string

The device token to use as the target for the push notification. Only used when the target type is SINGLE_DEVICE

targetTopic

string

The name of the topic to use as the target for the push notification. Only used when the target type is TOPIC.

targetType

ALL_DEVICES, SINGLE_DEVICE, TOPIC

The target of the push notification.

ALL_DEVICES

title

string

the title of the push notification

Curl sample
curl -X POST "https://cloud.gluonhq.com/3/push/enterprise/notification" \
     -H "Authorization: Gluon YOUR_SERVER_KEY"
     --data "body=Sample%20Body" \
     --data "title=Sample%20Title" \
     --data "expirationType=HOURS" \
     --data "expirationAmount=1" \
     --data "priority=HIGH" \
     --data "invisible=false" \
     --data "targetType=TOPIC" \
     --data "targetTopic=Topic1"
Java Client
CloudLinkConfig config = new CloudLinkConfig(YOUR_SERVER_KEY);
CloudLinkClient client = new CloudLinkClient(config);

PushNotification pushNotification = new PushNotification();
pushNotification.setBody("Sample Body");
pushNotification.setTitle("Sample Title");
pushNotification.setExpirationType(PushNotification.ExpirationType.HOURS);
pushNotification.setExpirationAmount(1);
pushNotification.setPriority(PushNotification.Priority.HIGH);
pushNotification.getTarget().setType(PushNotificationTarget.Type.TOPIC);
pushNotification.getTarget().setTopic("Topic1");
client.sendPushNotification(pushNotification);

9.7.5. Applying Push Notifications in Mobile Client

PushClient

To activate push notifications on your Gluon Mobile application, you will need a reference to the PushClient and call the enable method.

PushClient pushClient = new PushClient();
pushClient.enable(); // enable push notifications

This will trigger a message on an iOS device asking the user to confirm that push notifications are allowed for the application. On Android, if the user has not yet installed or activated Google Play services, a message will be shown to ask for permission to install and/or activate Google Play services on the device.

pom.xml

Don’t forget to add the Attach device, push-notifications and runtime-args services in the pom.xml file of your Gluon Mobile project.

pom.xml
<dependencies>
    ...
    <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>
    ...
</dependencies>
...
<plugin>
    <groupId>com.gluonhq</groupId>
    ...
    <configuration>
        ...
        <target>${gluonfx.target}</target>
        <attachList>
            <list>device</list>
            <list>display</list>
            <list>lifecycle</list>
            <list>push-notifications</list>
            <list>runtime-args</list>
            ...
    ...
</plugins>
Android configuration

For Android, we need to add the google-services.json file to the src/main/resources folder. This file can be downloaded from the project’s Firebase console.

iOS configuration

First make sure to configure your app so it can use Push notifications in the Apple Developer Center. You can follow this step-by-step guide.

As signing identity, use the certificate selected during the provisioning profile generation.

When deploying to your iOS device, the provisioning profile will be downloaded. If this is not the case, you still can install it manually: Open Xcode, connect your device, go to Window → Device, and at the bottom left there is a small engine icon, click to see the installed provisioning profiles, and then click on the + button to add this one.

Note that this provisioning profile contains the entitlements that match those installed within the app, required to enable push notifications.

The apsEnvironment property needs to match the match the used environment (development or production).

Build your app

When you’ve configured your project, all you have to do is building your app as usual with mvn -Pandroid gluonfx:build gluonfx:package for Android or mvn -Pios gluonfx:build gluonfx:package for iOS.

10. Samples

10.1. HelloFX - Native application

To learn how to build and run the HelloFX application sample natively on each platform, using GluonFX plugin for Maven, check the "HelloFX Sample" section under each of the platforms:

10.2. HelloSharedLib - Native shared library

To learn how to build the HelloSharedLib shared library sample natively on each platform, using GluonFX plugin for Maven, check the "HelloSharedLib Sample" section under each of the platforms:

10.3. Gluon Samples

Gluon also has curated a list of samples to help you get started with our products. These samples include everything including mobile, native images, cloudlink etc. We walk through the process of creation of each of these samples to make it easier for you to work with them.

Please check our Samples page for more information.

11. Scene Builder

The latest version of Scene Builder can be downloaded from the Gluon website.

Open Source

Scene Builder is open-sourced, and licensed under the BSD-3 license. Its source code is hosted under Gluon organization in Github.

11.1. Installation

Download the correct installer for your platform from the Scene Builder download page. The installation details are different for each platform but should be straight-forward.

As of Scene Builder 8.3.0, the Windows installer will let you choose the installation folder, but please note that you will need to run as administrator (right-click, run as administrator) if you want to install in a system-wide location.

11.2. Library Manager

Up until Scene Builder version 8.1.1, custom controls could be added with the menu button available at the Library panel. Selecting the option Import JAR/FXML File…​ from the drop down menu allowed the user browsing locally for jar or FXML files in his file system. Those files were added to the Custom Library Folder, a local folder that the user could access and manually add more files or remove them.

0. Open SB - Library Button - Former mode

From Scene Builder version 8.2.0, a new function was added, allowing the user not only to import local jar or FXML files as before, but also to import jars from a number of repositories, remote and local, public and even private ones.

To import libraries either from disk or from repositories, the menu item JAR/FXML Manager gives access to a new dialog, and replaces the old menu item.

1. Open SB - Library Button

11.2.1. The Library Manager dialog

The top part of the dialog contains a list of the existing libraries (jars and FXML files), if any.

2. Select Library Manager

For each library, the user can edit or delete it. Editing an FXML will open it in Scene Builder. Editing a jar file will open a dialog where the user can preview and select or unselect different components (if any) of that jar that will be added to the Custom panel.

On the lower part of the dialog, there are different actions that the user can perform.

Search Repositories

The user can type a name of a library: full name or part of its group id, or full name or part of it of the artifact id, following the usual naming convention.

For instance, if the user wants to download and install the latest release of com.gluonhq:charm, he can search just for charm:

3.0 New Search Repository option

All the results listed from all possible artifacts with that keyword, are retrieved from a given set of repositories:

  • Maven Central

  • Jcenter

  • Sonatype (releases)

  • Gluon Nexus (releases)

  • Local M2 (releases)

The results don’t include the version number, and this is only retrieved if the artifact is finally installed.

Note: Only latest releases will be resolved with this option. For any other version, including snapshots, the user has to use the option Manually Add Libraries from repository (see below).

By selecting the desired artifact, and clicking Add JAR, the artifact will be resolved from one of the repositories, downloading the latest release version, and installing it into the local M2 repository.

3.1 Resolve artifact

Then the jar will be scanned and a dialog will show any component that can be added to the Custom panel, typically classes that are assignable from Node. The user can select or unselect those components that should be added or excluded from the Custom panel. Also, some components can be previewed. The user has to click on Import Components to finish the process.

3.2 Select components

Once the dialog is closed, the new library will show up on the list. Again, it can be edited, to modify the list of included components, or deleted, to remove the jar and its components from the Custom panel.

3.3 Installed artifact

Note: When an artifact is removed from the list, it will just be removed from Scene Builder, but the artifact won’t be removed from the local M2 repository.

Manually add Library from repository

If the user knows the exact names of groupID and artifactID, he can select from all the available versions for that artifact, including snapshots, from an initial set of repositories:

  • Maven Central

  • Jcenter

  • Sonatype (releases and snapshots)

  • Gluon Nexus (releases)

  • Local M2 (releases and snapshots)

that can be extended as it will be shown later.

4.0 Manually search for groupID and artifactID

Once a version is selected, clicking Add JAR will resolve the artifact, downloading and installing it into the local M2 repository.

4.1. Select version and install

The jar will be scanned and available components can be imported, after the user clicks on Import Components.

4.2. Select components
Add Library/FXML from file system

This option allows the user browsing locally for jar or FXML files, as in previous versions of Scene Builder. The files will be installed in the Custom Library Folder.

Manage Repositories

Finally, the user can manage the repositories where artifacts are resolved from.

5.0. Repository Manager

Initially, the preset list of remote repositories is listed. These are not editable and can’t be removed.

By clicking on Add Repository, the user can add new repositories to the list, both public or private.

A new repository requires a name and a valid URL. If it is private, the credentials are required as well. The test button will perform a connection to the given URL to check if it is valid or not, and if private, if the credentials are valid as well.

5.1. Add a new private repository

Note: The credentials will be stored locally in the user preferences. They will be used only when installing libraries from the private repository.

Finally, the user repository will be added to the list, with the possibility of edition or deletion.

5.2. It will be listed

11.3. Custom Panel

Finally, all the components imported from either local or remote repositories or from the local file system, will be listed in the Custom panel, ready to be used.

6. Components ready to drag and drop

11.4. Developing Applications Using Gluon

11.4.1. Gluon Panel

Since Scene Builder 8.3.0, a Gluon panel is included by default to support the easy creation of Gluon based apps using Scene Builder and Gluon Mobile.

When using Gluon controls, don’t forget to use the Gluon Mobile preview theme as shown in the screenshot below, or controls might not work correctly.

6. Components ready to drag and drop

As of Scene Builder 8.4.0 you can also preview your controls using a Gluon Swatch and Theme. The theme can either be Gluon Mobile Light or Gluon Mobile Dark, the Swatch is the primary color used, it is what affects the appearance of the controls the most. In your code you should then use the Swatch and Theme API to effectively have the same appearance.

6. Components ready to drag and drop

11.4.2. Designing Screens

With Gluon a screen is typically created using a View. That View is usually composed of an AppBar at the top and some content at the center.

When creating Views don’t forget to set their name.

If you want to create your View in FXML with an AppBar also defined, you should disable the globally provided AppBar in your code by toggling its visibility:

MobileApplication.getInstance().getAppBar().setVisible(false);

View also has a bottom property, for instance you can set a BottomNavigation on that position.

7. View with AppBar and BottomNavigation

11.4.3. Testing Your Designs In Different Form Factors

Scene Builder 8.4.0 brings with it the possibility of testing your designs against different form factors. To this effect you have several options:

8. Defining different form factors for the root container

As you can see on the previous image there are sizes for Desktop, Tablet and Mobile Phones. The default size can be changed in the Preferences window.

If you want to preview your design live, you can by selecting the Preview → Show Preview in Window (or Ctrl + P) menu item. This will open up a window with a Scene containing the FXML you defined.

With the preview window open you can change it’s size by dragging the window borders or to a specific value from the menu:

8. Changing the size of the Preview Window

12. Conclusion

In case you have any questions, your first stop should be the Gluon support page, where you can access the latest samples, documentation and more. Gluon also recommends the community to support each other over at the Gluon StackOverflow page. Finally, Gluon offers commercial support as well, to kickstart your projects.

We encourage you to start developing new projects using Gluon offering. We can’t wait to see what you create!