Java Platform, Standard Edition Deployment Guide
Contents    Previous    Next

13 Preloaders for JavaFX Applications

This section explains the preloaders that are used for JavaFX applications, and describes how to implement a custom preloader. Custom preloaders are not available for Java applications, however, the default preloader can be customized.

During the second phase of startup, a preloader application runs, either the default application in the JavaFX Runtime or a custom application that you supply. See Section 4.2, "Application Startup Process, Experience, and Customization" for information about how a preloader fits into the startup flow.

A custom preloader application is optional and can be used to tune the application loading and startup experience. For example, use of a preloader can help to reduce perceived application startup time by showing some content to the user earlier, such as a progress indicator or login prompt.

A preloader application can also be used to present custom messaging to the user. For example, you can explain what is currently happening and what the user will be asked to do next, such as grant permissions to the application, or you could create a preloader to present custom error messages.

Not every application needs a custom preloader. For example, if your application is small and does not have special requirements such as permissions, then it probably starts quickly and the default preloader is sufficient. Even for larger applications, the default preloader included with the JavaFX Runtime can be a good choice, because it is loaded from the client machine rather than the network.

This section contains the following topics:

See Section 4.2, "Application Startup Process, Experience, and Customization" for information about how to customize the default preloader.

For information on customizing the preloader for Java applications, see Chapter 14, "Customizing the Loading Experience."

13.1 Implementing a Custom Preloader

A custom preloader is a specialized JavaFX application that extends the javafx.application.Preloader class. Because the Preloader class is an extension of javafx.application.Application, a custom preloader has the same life cycle and can use all of the features of the JavaFX Runtime.

The preloader startup sequence is shown in relation to the application startup in Figure 13-1. The preloader application is started before the main application and gets notification of the progress of the loading application resources, application initialization, and startup, as well as of errors.

Example 13-1 shows a simple preloader that uses the ProgressBar control to show the loading progress.

As a regular JavaFX application, the FirstPreloader class uses the start() method to create a scene to display the loading progress. Updates on progress are delivered to the preloader using the handleProgressNotification() method, and the FirstPreloader implementation uses them to update the UI.

The preloader and main application have different Stage objects, and the preloader must take care of showing and hiding its own stage when needed. In Example 13-1, the preloader stage is hidden after notification is received that the start() method of the main application is about to be called.

The implementation of the FirstPreloader class illustrates the main concept and works in many scenarios, but it does not provide the best user experience for all use cases. See Section 13.3, "Preloader Code Examples" for examples of how to improve the FirstPreloader class.

13.2 Packaging an Application with a Preloader

Packaging applications with preloaders has some special requirements.

First, in most cases, the code for the preloader must be packaged into one or more JAR files that are separate from the rest of application. This enables faster loading when the application is deployed on the web. Using a single JAR file for both application and preloader code can be a good choice for some specialized cases, for example if the application is run in standalone mode only. In NetBeans IDE, the JAR files are packaged separately by creating two projects: one for the main application and a special JavaFX preloader project for the preloader. See Section 13.2.1, "Packaging a Preloader Application in NetBeans IDE."

Second, application deployment descriptors must include information about which class belongs to the preloader and where the preloader code is. The way to specify it depends on what tools you use for packaging. For more information about tools, see Section 5.3.1, "Java Packaging Tools."

All of the packaging tools produce a deployment descriptor that includes the preloader, as in Example 13-2. In this example, the main application is called AnimatedCircles and the preloader application is called FirstPreloader.

The manifest must also contain the class path to the preloader, shown in Example 13-3.

13.2.1 Packaging a Preloader Application in NetBeans IDE

If you are using NetBeans IDE, in the main application you can specify either another NetBeans project that contains the main preloader class, or a JAR file in which the preloader is packaged.

The following procedures show two ways to package a preloader in NetBeans IDE, depending on your project configuration. You can either create a new NetBeans project and choose a preloader option, or you can add a preloader to an existing NetBeans project. Both procedures use the preloader class from Example 13-1.

To create a new application with a preloader in NetBeans IDE:

  1. On the File menu, choose New Project.

  2. Select the JavaFX category and JavaFX Application as the project type. Click Next.

  3. Enter FirstApp as a project name and choose Create Custom Preloader. Click Finish.

    Netbeans IDE creates two new projects for you: a FirstApp-Preloader project with basic implementation of a custom preloader, and FirstApp project with a sample JavaFX Application using your custom preloader.

  4. Open the SimplePreloader class in Source Packages in the FirstApp-Preloader project.

  5. Replace the implementation of the SimplePreloader class with the implementation of the FirstPreloader class, or any other sample from this page.

    Be sure to fix imports if needed by going to the Source menu and choosing Fix Imports.

  6. Select the FirstApp project and run Clean and Build to build both the sample application and the preloader.

    The artifacts are placed in the dist folder in the FirstApp project.

  7. Test the artifacts by running them in Netbeans.


    Tip:

    You can launch your application as standalone or in a browser by choosing a Run category in Project Properties, or you can directly open the build artifacts.

    Note that for standalone launch, the preloader might be not visible if it displays loading progress only, because there is nothing to load. Even when testing web launch from a local hard drive, the preloader might show up for only a very short time.

To add a preloader to an existing NetBeans project:

  1. Create a separate NetBeans project of type JavaFX Preloader for the preloader class. In the example, the project name is FirstPreloader, which contains the firstpreloader package and the code for the FirstPreloader class.

  2. In the Project Properties for the main application, click the Run category.

  3. Select the check box Use Preloader, Click Browse, then choose the NetBeans project for the preloader. The Preloader Class field is populated by default, as shown in Figure 13-2.


    Note:

    As an alternative to selecting a NetBeans project for the preloader, when you click Browse you have the option of selecting a preloader JAR file.

  4. Click OK to close the Project Properties dialog box.

  5. Right-click the main application, and choose Clean and Build.

    The main application files are created for deployment in the dist directory, and the preloader JAR file is placed in a lib subdirectory. All of the necessary JNLP and manifest entries are handled by the IDE.

13.2.2 Packaging a Preloader Application in an Ant Task

Ant users must specify information about the preloader class and JAR files in both the <fx:jar> and <fx:deploy> tasks. Setting the proper parameters in the <fx:jar> task ensures that the preloader is registered for standalone applications. Setting the proper parameters in the <fx:deploy> task creates the configuration for web deployment.

Some settings are required in other parts of the Ant script. The preloader main class is specified as part of the <fx:deploy> element, as shown in Example 13-4.

Preloader resources are marked with the requiredFor="preloader" attribute in the description of application resources, nested under <fx:application>, as shown in Example 13-5.

With the help of the refid attribute creating a reference to an id attribute, elements can be reused to reduce code duplication. The preloader settings for the <fx:jar> and <fx:deploy> tasks are shown in Example 13-6.

See Example 10-3 for another preloader configuration in a full Ant task. In that example, both the preloader and the main application JAR files are signed in the <fx:signjar> task. If the preloader JAR file is unsigned and the main application JAR file is signed, then a multipart deployment descriptor is needed. Packaging is similar to any other JavaFX application using a mix of signed and unsigned code. For more information, see Section 5.7.2, "Application Resources."

Note the following preloader-specific details:

  • The name of the preloader class is always specified in the main application descriptor, as in Example 13-4.

  • In most cases, it is a good idea to keep the preloader JAR files in the main application descriptor so they start loading sooner.

The reasoning for the last point is as follows. There are two <fx:deploy> tasks to package this application, which generate two different JNLP files: one for the main application and another extension. The application starts from the link to the main JNLP, so whatever is referenced from the main JNLP file can start loading sooner, and is ready faster.

13.3 Preloader Code Examples

The following code examples demonstrate various uses of preloaders:

13.3.1 Show the Preloader Only if Needed

If the application runs standalone or is loaded from the web cache, then the preloader does not get any progress notifications because there is nothing to load, and the application will likely start quickly.

Using the FirstPreloader example as implemented in Example 13-1, users see only the preloader stage briefly with 0 percent progress. Unless the application is embedded in a browser, a window also pops up that is briefly visible. In this case, a better user experience is to show nothing until the first progress notification.

When the application is embedded in a web page, something needs to be shown to avoid having a gray box where the application will appear. One possible approach is to display the HTML splash screen until the preloader has something to display or, if the preloader does not get any events, until the application is ready. Another option is to show a simplified version of the preloader, and add a progress indicator after the first progress notification is received.

Example 13-7 shows how to improve the relevant parts of the FirstPreloader implementation:

  • Do not show the progress indicator until the first progress notification.

  • If the preloader stage is not embedded, do not show the progress bar until the first progress notification.

See Section 13.3.3, "Using JavaScript with a Preloader" for an example of how to postpone hiding the splash screen.

13.3.2 Enhance Visual Transitions

The last state change notification received by the preloader before the application starts is StateChangeNotification.Type.BEFORE_START. After the notification is processed, the application's start() method is called. However, it can take time before the application is ready to display its stage after the start() method is called. If the preloader stage is already hidden, then there could be a period of time when the application shows nothing on the screen. When the application is embedded in a web page, this can result in a hole in the web page effect.

For this and other reasons, hiding the preloader instantly might not be the best visual transition from preloader to application. One approach to improve the visual transition between preloader and application is shown in Example 13-8. If this FirstPreloader example is used for an application embedded in a web page, it will fade out over a period of 1 second instead of hiding instantly.

If the preloader and application cooperate, then the transition is even smoother. See Section 13.3.6, "Cooperation of Preloader and Application: Sharing the Stage" for an example of a preloader that fades into the application.

If the application takes time to initialize, then it can be helpful to use a custom notification to initiate the transition from preloader to application when the application is ready. See Section 13.3.4, "Using a Preloader to Display the Application Initialization Progress" for further information.

13.3.3 Using JavaScript with a Preloader

Because a JavaFX application preloader has access to application features such as parameters and host services, the preloader can use JavaScript to communicate to the web page in which an application is embedded.

In Example 13-9, JavaScript access is used to create a preloader that displays the loading progress in the HTML splash screen and hides the splash screen only when the application is ready. The code uses the following two JavaScript methods, which must be provided by the web page:

  • hide() to hide the HTML splash screen

  • progress(p) to update the progress

It is assumed that there is a custom HTML splash screen that is not hidden by default.

Example 13-10 shows a sample web page template that uses the preloader in Example 13-9. When this template page is processed during packaging, #DT.SCRIPT.URL# and #DT.EMBED.CODE.ONLOAD# will be replaced with code to embed the JavaFX application into the web page. For more information about templates, see <fx:template> in the JavaFX Ant reference.

13.3.4 Using a Preloader to Display the Application Initialization Progress

A JavaFX application can pass information about events to a preloader by using custom notifications. For example, the preloader can be used to display the application initialization progress.

Technically, any class implementing the Preloader.PreloaderNotification interface can serve as a custom notification, and the application can send it to the preloader by using the Application.notifyPreloader()method. On the preloader side, the application notification is delivered to the handleApplicationNotification() method.

Example 13-11 is a variation of the FirstPreloader example. It does not hide the preloader after notification of application startup is received. It waits for application-specific notifications, displays the progress notifications, and hides the splash screen after the application sends a state change notification.

Example 13-11 Preloader to Display Progress of Application Initialization and Loading

public class LongAppInitPreloader extends Preloader {
    ProgressBar bar;
    Stage stage;
    boolean noLoadingProgress = true;
 
    private Scene createPreloaderScene() {
        bar = new ProgressBar(0);
        BorderPane p = new BorderPane();
        p.setCenter(bar);
        return new Scene(p, 300, 150);
    }
 
    public void start(Stage stage) throws Exception {
        this.stage = stage;
        stage.setScene(createPreloaderScene());
        stage.show();
    }
 
    @Override
    public void handleProgressNotification(ProgressNotification pn) {
        //application loading progress is rescaled to be first 50%
        //Even if there is nothing to load 0% and 100% events can be
        // delivered
        if (pn.getProgress() != 1.0 || !noLoadingProgress) {
          bar.setProgress(pn.getProgress()/2);
          if (pn.getProgress() > 0) {
              noLoadingProgress = false;
          }
        }
    }
 
    @Override
    public void handleStateChangeNotification(StateChangeNotification evt) {
        //ignore, hide after application signals it is ready
    }
 
    @Override
    public void handleApplicationNotification(PreloaderNotification pn) {
        if (pn instanceof ProgressNotification) {
           //expect application to send us progress notifications 
           //with progress ranging from 0 to 1.0
           double v = ((ProgressNotification) pn).getProgress();
           if (!noLoadingProgress) {
               //if we were receiving loading progress notifications 
               //then progress is already at 50%. 
               //Rescale application progress to start from 50%               
               v = 0.5 + v/2;
           }
           bar.setProgress(v);            
        } else if (pn instanceof StateChangeNotification) {
            //hide after get any state update from application
            stage.hide();
        }
    }  
 }

In Example 13-11, note that the same progress bar is used to display the progress of both the application initialization and loading. For simplicity, 50 percent is reserved for each phase. However, if the loading phase is skipped, for example when the application is launched as standalone, then the entire progress bar is devoted to displaying the progress of the application initialization.

Example 13-12 shows the code on the application side. The longStart() method is used to simulate a lengthy initialization process that happens on a background thread. After initialization is completed, the ready property is updated, which makes the application stage visible. During initialization, intermediate progress notifications are generated. At the end of initialization, the StateChangeNotification is sent, which causes the preloader to hide itself.

In this example, standard events are reused, but in general the application can send arbitrary data to the preloader. For example, for application loading, image collection notifications can include sample preview images and so on.

13.3.5 Cooperation of Preloader and Application: A Login Preloader

As part of StateChangeNotification, the preloader receives a reference to the application, which enables the preloader to cooperate closely with the application.

The example in this section shows how to use this cooperation in a login preloader, which requests user credentials while the application is loading, then passes them to the application.

To cooperate, this preloader and application share the CredentialsConsumer interface, which the preloader uses to pass credentials to the application. In addition to implementing a shared interface, the only other special thing in this sample is that the application does not show itself until it has both user credentials and a reference to a Stage object.

Example 13-13 shows the application code for the login preloader.

The preloader stage is displayed unconditionally, because the user must provide credentials. However, the preloader is not hidden when the application is ready to start unless there are credentials to pass to the application.

To pass credentials, you can cast a reference to the application from StateChangeNotification to CredentialsConsumer, assuming the application implements it.

In Example 13-14, the login pane UI from the previous example is simplistic, but it shows how to adapt it to execution mode. If there is no progress to display, then there is no point to adding a progress bar to the UI. Also, if the application has finished loading but is still waiting for user input, then the UI can be simplified by hiding unneeded progress.

Example 13-14 Login Preloader Code

public class LoginPreloader extends Preloader {
    public static interface CredentialsConsumer {
        public void setCredential(String user, String password);
    }
    
    Stage stage = null;
    ProgressBar bar = null;
    CredentialsConsumer consumer = null;
    String username = null;
    String password = null;
    
    private Scene createLoginScene() {
        VBox vbox = new VBox();
 
        final TextField userNameBox = new TextField();
        userNameBox.setPromptText("name");
        vbox.getChildren().add(userNameBox);
        
        final PasswordField passwordBox = new PasswordField();
        passwordBox.setPromptText("password");
        vbox.getChildren().add(passwordBox);
        
        final Button button = new Button("Log in");
        button.setOnAction(new EventHandler<ActionEvent>(){
            public void handle(ActionEvent t) {
                // Save credentials
                username = userNameBox.getText();
                password = passwordBox.getText();
                
                // Do not allow any further edits
                userNameBox.setEditable(false);
                passwordBox.setEditable(false);
                button.setDisable(true);
                
                // Hide if app is ready
                mayBeHide();
            }
        });
        vbox.getChildren().add(button);
        
        bar = new ProgressBar(0);
        vbox.getChildren().add(bar);
        bar.setVisible(false);
        
        Scene sc = new Scene(vbox, 200, 200);
        return sc;
    }
    
    @Override
    public void start(Stage stage) throws Exception {
        this.stage = stage;
        stage.setScene(createLoginScene());
        stage.show();
    }
    
    @Override
    public void handleProgressNotification(ProgressNotification pn) {
        bar.setProgress(pn.getProgress());
        if (pn.getProgress() > 0 && pn.getProgress() < 1.0) {
            bar.setVisible(true);
        }
    }
 
    private void mayBeHide() {
        if (stage.isShowing() && username != null && consumer != null) {
            consumer.setCredential(username, password);
            Platform.runLater(new Runnable() {
                public void run() {
                   stage.hide();
                }
            });
        }
    }
    
    @Override
    public void handleStateChangeNotification(StateChangeNotification evt) {
        if (evt.getType() == StateChangeNotification.Type.BEFORE_START) {
            //application is loaded => hide progress bar
            bar.setVisible(false);
            
            consumer = (CredentialsConsumer) evt.getApplication();
            //hide preloader if credentials are entered
            mayBeHide();
        }
    }    
}

Note that close cooperation between the preloader and application is subject to mixed code restrictions unless both the preloader and application are in the same trust domain, in other words both are privileged or both are sandbox. See Chapter 27, "Mixing Privileged Code and Sandbox Code" for information.

13.3.6 Cooperation of Preloader and Application: Sharing the Stage

This section demonstrates how to use cooperation between the preloader and the application to improve the transition from preloader to application.

Example 13-15 shows how the preloader and application share the same stage, and the preloader fades into the application when the application is ready. As in Example 13-14, the preloader and application need to share the SharedScene interface.

The Application class implements it to provide the preloader with access to the application scene. The preloader later uses it for setup transition.

Now, the interface must be implemented. The code in Example 13-16 shows that the application is active during the transition.

On the preloader side, instead of hiding the preloader stage, the code initiates a fade-in transition by inserting the application scene behind the preloader scene and fading out the preloader scene over time. After the fade-out is finished, the preloader is removed from the scene so the application can own the stage and scene.

13.4 Performance Tips

Because preloaders are displayed while the main application is loading, it is critical that they load quickly and run smoothly.

Use the following guidelines to ensure your preloaders perform well:

The following guidelines are applicable to both the main application and the preloader. See also Chapter 18, "Coding Tips" for general tips.

  • Avoid lengthy operations on the JavaFX application thread.

    Avoid blocking the JavaFX thread, which pauses any UI updates and event processing. To avoid freezing the application UI, use the JavaFX Worker API and offload lengthy operations to other threads.

  • Keep the start() method implementation lightweight.

    Do more work in the init() method to unclog the JavaFX application thread.

  • Enable embedding when packaging your application for web deployment.

    Embedding a deployment descriptor (JNLP) and security certificates (if needed) reduces the time needed to collect all the information about the application and helps to start the preloader and application faster.

Contents    Previous    Next

Copyright © 1993, 2017, Oracle and/or its affiliates. All rights reserved.