Search code examples
javajavafxjavafx-8

How can I call a method in a JavaFX Application's preloader instance from the main Application's init() method


In a JavaFX application's init() method I am doing some checks, one of them is a check to see if it can connect to a web address based using Http response codes. This app also has a preloader that runs while these checks are happening.

Depending on the response code, I want it to display an alert window during the preloader application's lifecycle

I am not sure if this is possible using the current javafx preloader class, but is there any workaround that could achieve this?

Below is an SSCCE of what I would want

The application

public class MainApplicationLauncher extends Application implements Initializable{

    ...

    public void init() throws Exception {       
          
        for (int i = 1; i <= COUNT_LIMIT; i++) {
            double progress =(double) i/10;
            System.out.println("progress: " +  progress);         
            
            notifyPreloader(new ProgressNotification(progress));
            Thread.sleep(500);
        }
        
        try {
            URL url = new URL("https://example.com");
            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
            connection.setRequestMethod("GET");
            connection.connect();           
            int code = connection.getResponseCode();
            System.out.println("Response code of the object is "+code);
            if (code==200) {
                System.out.println("Connected to the internet!");
            }else if (code==503){
                        //call the handleConnectionWarning() in preloader
                System.out.println("server down !");
            }
        } catch (UnknownHostException e) {
                //call the handleConnectionWarning() in preloader
            System.out.println("cannot connect to the internet!");
        }

    public static void main(String[] args) {        
       System.setProperty("javafx.preloader", MainPreloader.class.getCanonicalName());
        Application.launch(MainApplicationLauncher.class, args);
   }
}

The preloader

public class MyPreloader extends Preloader {

...

//Method that should be called from application method

public void handleConnectionWarning() {
        Alert alert = new Alert(AlertType.WARNING);
        alert.setTitle("Server is Offline");
        alert.setHeaderText("Cannot connect to service");
        alert.setContentText("Please check your connection");

        alert.showAndWait();
    }

}

Are there any ways to do this?


Solution

  • Preloader

    If you want to continue using Preloader for your splash screen, then you can call the desired method via a notification. Create your own notification class:

    // You can modify this class to carry information to the Preloader, such
    // as a message indicating what kind of failure occurred.
    public class ConnectionFailedNotification implements Preloader.PreloaderNotification {}
    

    Send it to your Preloader:

    notifyPreloader(new ConnectionFailedNotification());
    

    And handle it in said Preloader:

    @Override
    public void handleApplicationNotification(PreloaderNotification info) {
        if (info instanceof ConnectionFailedNotification) {
            handleConnectionWarning();
        }
        // ...
    }
    

    No Preloader

    The Preloader class makes more sense when you're deploying your application via Java Web Start (i.e., to web browsers), where the code has to be downloaded before it can be used. But Java Web Start is no longer supported (though I think there may be a third-party maintaining something at least similar). Given your application is likely targeted for a simple desktop deployment, using Preloader can make things unnecessarily complicated. Instead consider simply updating the primary stage's content after initialization.

    Move your init() stuff into a Task implementation:

    import java.io.IOException;
    import javafx.concurrent.Task;
    
    public class InitTask extends Task<Void> {
    
        private static final int COUNT_LIMIT = 10;
    
        private final boolean shouldSucceed;
    
        public InitTask(boolean shouldSucceed) {
            this.shouldSucceed = shouldSucceed;
        }
    
        @Override
        protected Void call() throws Exception {
            for (int i = 1; i <= COUNT_LIMIT; i++) {
                updateProgress(i, COUNT_LIMIT);
                Thread.sleep(500);
            }
            
            // could use a Boolean return type for this, but your real code seems
            // more complicated than a simple "yes" / "no" response. If you do
            // change the implementation to use a return value, note that you would
            // then need to check that return value in the 'onSucceeded' handler
            if (!shouldSucceed) {
                throw new IOException("service unavailable"); // failure
            }
            return null; // success
        }
        
    }
    

    And then launch that task on a background thread:

    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.scene.Scene;
    import javafx.scene.control.Alert;
    import javafx.scene.control.Label;
    import javafx.scene.control.ProgressBar;
    import javafx.scene.control.Alert.AlertType;
    import javafx.scene.layout.StackPane;
    import javafx.stage.Stage;;
    
    public class App extends Application {
        
        @Override
        public void start(Stage primaryStage) throws Exception {
            var task = new InitTask(false); // change to 'true' to simulate success
            task.setOnSucceeded(e -> primaryStage.getScene().setRoot(createMainScreen()));
            task.setOnFailed(e -> {
                var alert = new Alert(AlertType.WARNING);
                alert.initOwner(primaryStage);
                alert.setTitle("Server Offline");
                alert.setHeaderText("Cannot connect to service");
                alert.setContentText("Please check your connection");
                alert.showAndWait();
    
                Platform.exit();
            });
    
            // Also see the java.util.concurrent.Executor framework
            var thread = new Thread(task, "init-thread");
            thread.setDaemon(true);
            thread.start();
    
            var scene = new Scene(createSplashScreen(task), 600, 400);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        private StackPane createSplashScreen(InitTask task) {
            var bar = new ProgressBar();
            bar.progressProperty().bind(task.progressProperty());
            return new StackPane(bar);
        }
    
        private StackPane createMainScreen() {
            return new StackPane(new Label("Hello, World!"));
        }
    }
    

    Side Notes

    Your Application subclass should not implement Initializable. That subclass represents the entire application and should never be used as an FXML controller.