Overview

JavaFX: How to easily implement an application preloader

No Comments

Introduction

Have you ever been in situation that you developed an awesome JavaFX application but it is taking too long to initially load due to non-JavaFX prerequisites?

Maybe you are waiting for the connection to the database to initialize, checking for updates, testing connection or retrieving data from server…

Common application launch

The most basic approach for launching JavaFX applications is by invoking an Application.launch() method inside main method, which normally looks like this:

public class MyApplication extends Application {
   public static void main(String[] args) {
      launch(args);
   }
 
   @Override
   public void start(Stage primaryStage) throws Exception {
      // Do some heavy lifting
      // Load primary stage
   }
}

Because the Application#start method is invoked on JavaFX Application Thread, the “heavy lifting” is going to cause a long delay between the moment user has ran the application and the moment the application window is actually shown.

Launch with preloader

If we browse through the source code of the Application.launch() method we can see that under the hood LauncherImpl#launchApplication is called. Going deeper inside LauncherImpl we can see that actually the private static method launchApplication1 is called with preloaderClass argument set to null. This doesn’t help us much if we wish to use the preloader, so we are going to make a small shortcut and directly use the launchApplication(appClass, preloaderClass, args) method of LauncherImpl in order to launch the JavaFX application from main method. Let’s look at header of launchApplication:

/**
* This method is called by the standalone launcher.
* It must not be called more than once or an exception will be thrown.
*
* Note that it is always called on a thread other than the FX application
* thread, since that thread is only created at startup.
*
* @param appClass application class
* @param preloaderClass preloader class, may be null
* @param args command line arguments
*/
public static void launchApplication(final Class<? extends Application> appClass,
   final Class<? extends Preloader> preloaderClass,
   final String[] args) {
}

Fine, we now know how to launch JavaFX application with a preloader, so let’s start with some example Main class:

import com.sun.javafx.application.LauncherImpl;
 
public class Main {
   public static void main(String[] args) {
      LauncherImpl.launchApplication(MyApplication.class, MyPreloader.class, args);
   }
}

In this example the MyApplication class extends javafx.application.Application and implements its #start and #init methods, let us consider a simple example:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MyApplication extends Application {
 
   @Override
   public void init() throws Exception {
      // Do some heavy lifting
   }
 
   @Override
   public void start(Stage primaryStage) throws Exception {
      BorderPane root = new BorderPane(new Label("Loading complete!"));
      Scene scene = new Scene(root);
      primaryStage.setWidth(800);
      primaryStage.setHeight(600);
      primaryStage.setScene(scene);
      primaryStage.show();
   }
}

It can be easily observed that the “heavy lifting” is moved to #init instead of #start. We will be later back to this, let’s consider how preloader should look like first:

import javafx.application.Preloader;
import javafx.application.Preloader.StateChangeNotification.Type;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MyPreloader extends Preloader {
private Stage preloaderStage;
 
    @Override
    public void start(Stage primaryStage) throws Exception {
       this.preloaderStage = primaryStage;
 
       VBox loading = new VBox(20);
       loading.setMaxWidth(Region.USE_PREF_SIZE);
       loading.setMaxHeight(Region.USE_PREF_SIZE);
       loading.getChildren().add(new ProgressBar());
       loading.getChildren().add(new Label("Please wait..."));
 
       BorderPane root = new BorderPane(loading);
       Scene scene = new Scene(root);
 
       primaryStage.setWidth(800);
       primaryStage.setHeight(600);
       primaryStage.setScene(scene);
       primaryStage.show();
   }
 
   @Override
   public void handleStateChangeNotification(StateChangeNotification stateChangeNotification) {
      if (stateChangeNotification.getType() == Type.BEFORE_START) {
         preloaderStage.hide();
      }
   }
}

What we wish to achieve

preloader.showing

What the given diagram is describing is basically to show the preloader stage until our application is performing the “heavy lifting” and showing the main application scene when this work is done. The interesting thing about the JavaFX launch flow is that the process in which application is started are organized precisely in the following steps:

1. MyPreloader constructor called, thread: JavaFX Application Thread
2. MyPreloader#init (could be used to initialize preloader view), thread: JavaFX-Launcher
3. MyPreloader#start (showing preloader stage), thread: JavaFX Application Thread
4. BEFORE_LOAD
5. MyApplication constructor called, thread: JavaFX Application Thread
6. BEFORE_INIT
7. MyApplication#init (doing some heavy lifting), thread: JavaFX-Launcher
8. BEFORE_START
9. MyApplication#start (initialize and show primary application stage), thread: JavaFX Application Thread

Notice that constructors and start methods are called on the JavaFX Application Thread allowing interaction with UI components. To interact with the UI during init phase, use Platform.runLater(…) to schedule execution of code on JavaFX Application Thread.

The launch flow

For a better understanding of this simple example of creating your own JavaFX application preloader, please take a look at provided source code.
Source code for the example is available here.
The Java version used for example is 1.8.0_60.

Comment

Your email address will not be published. Required fields are marked *