Software development

Trying JavaFX

Topics
JavaFX, Software Development, Gradle, Java, UI

A year ago there was a blog post about VPN Wizard, a tool that was going to save time and money for our IT team. But it won’t save anything if people can't use it. This year people have reported problems running the wizard on high-dpi displays, e.g. text was too small on a 15" display with 4K resolution.

After playing for a few hours with Swing, we realized that the time had come to throw out some legacy code, and write something new. Actually only a small part of the code was discarded (the one that contained the Swing UI), so I believe we are doing our apps efficiently. :)

 

JavaFX pros and cons for VPN wizard

Good things:

  • Runs on all OSs that we have (Linux, OSX, Windows).

  • Builds on all OSs that we have.

  • CSS and declarative layouts.

  • Could be bundled with JRE.

Not so good things:

  • Not so many UI libs compared to what is available in JS. Many things that I have gotten used to in JS development, I’ve had to code myself.

  • Some build plugins don't work as expected or don't work at all, and there are no alternatives.

  • No livereload. You have to restart the app and navigate to the desired view to see changes after every update. This slows down development and especially the styling of the application.


I won’t go into every detail about JavaFX, but I will show some interesting snippets that may help if someone decides to start writing desktop UIs.


Building (with gradle 2.1+)

build.gradle

plugins {
  id 'java'
  id 'application'
  id 'edu.sc.seis.macAppBundle' version '2.1.1'
  id 'edu.sc.seis.launch4j' version '1.5.1'
}

mainClassName = 'com.futurice.intra.vpn.Main'

macAppBundle {
  mainClassName = project.mainClassName
}

launch4j {
  mainClassName = project.mainClassName
}

And you are ready to make Win build from your Mac, or OSX builds from your brand new Dell XPS:

//build version for windows (exe)
gradle launch4j

//build version for Mac
gradle createAppZip

There are also other gradle plugins, that do similar builds, but so far these work best for us.

FXML

FXML is very similar to html and android markup. Write some tags, apply styles, run and see nice UI.

Layouts could be nested in fxml:

<BorderPane>
  <center >
    <fx:include source="my_panel.fxml"/>
  </center>
</BorderPane>

Or in dynamically in controller:

rootLayoutBorderPane.setCenter(my_panel);


Controls

For controls I suggest checking out Ensemble 8 and ControlsFX.

Another nice tool is SceneBuilder. Gluon is making binary builds that can be downloaded from here: http://gluonhq.com/open-source/scene-builder/ You could use it standalone, or just integrate to your IDE. With IntelliJ Idea it was really nice to prototype views, switching back and forth between code and layout.

 

Data binding

Databinding is really easy. Define element in  .fxml:

<TextField fx:id="usernameField"/>

Add the same element as a private field with an annotation, and then just get/set values:

@FXML
private TextField usernameField;

usernameField.setText("Username");
usernameField.getText();

 

UI Scaling

Earlier I mentioned XPS, and there is a reason. One of the problems with the old Swing UI was that it was too small on high-dpi displays. It just doesn't respect the font scaling setting of the operating system. With JavaFX it is not a problem anymore.

Async call with progress bar

When the UI needs to send data to a remote server, it is good practice to use a new thread. That new thread could handle the server response, but it is not allowed to change elements in the UI. In JavaFX only GUI thread could change UI. Platform.runLater() is one of the methods to solve that limitation. Internally it will add a new Runnable to event queue that will be executed in GUI thread.

A simple label and a progress bar. Note that we don't set any values, allowing it to have infinite animation.

<Label fx:id="serverResponse" text="" visible="false" textFill="darkred" />
<ProgressBar fx:id="progressBar" visible="false" GridPane.columnIndex="1" />

And method that send data and show progressbar while waiting for response:

    protected sendDataToServer() {
        //hide initially and when resend
        serverResponse.setVisible(false);
        curState = STATE_SENDING_PASSWORD;
        // New Thread to keep gui responsive
        Thread send = new Thread() {
            public void run() {
                String response = myRemoteHttpCall("some value"); //remote service call
                curState = STATE_USER_INPUT;
                if (response != null) {
                    //as response come in another thread, Platform.runLater will run code in main UI thread
                    Platform.runLater(() -> {
                        //show error to user
                        serverResponse.setText("There was an error when sending the information:\n" + response);
                        serverResponse.setVisible(true);
                    });
                } else {
                    Platform.runLater(() -> {
                        wizard.next();
                    });
                    curState = STATE_USER_INPUT;
                }
            }
        };
        send.start();

        //separate thread to check progress and show progressbar
        Thread statusUpdates = new Thread(){
            public void run(){
                progressBar.setVisible(true);
                while(curState == STATE_SENDING_PASSWORD){
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        log.error("thread interrupted", e);
                    }
                }
                //hide when 
                progressBar.setVisible(false);
            }
        };
        statusUpdates.start();

        return false;
    }

 


JRE Bundling

When we can't rely on the user having the latest Java version, we can bundle the JRE inside our application.

macAppBundle {
  mainClassName = project.mainClassName
  bundleJRE = true
}

The current JRE will be included in the distribution. Unfortunately it doesn't work out of the box for Windows builds.


Summary

The intent of this article was to show how easy is to develop and build JavaFX desktop applications. These were just a few code samples that make development easier. You can find more examples in our public github repo: https://github.com/futurice/vpn-management-client or on the Internet (e.g. http://www.javafxtutorials.com/whatisjavafx/ )