2

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?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Matias S
  • 23
  • 4
  • 1
    Generally speaking, you should just launch your Application and get the GUI up and running. Maybe it's just a Splash screen. Then use Task to run your connection checking code on a background thread. Use the "OnSucceeded" property of Task to specify the action that you want to have happen when the background Task completes. If all is well, then load up your main screen, otherwise put up a different screen showing the problem. – DaveB Jan 02 '23 at 21:12
  • @DaveB To clarify, the preloader is currently serving as a splash screen to the main application. But so is using the Preloader class not viable? Because I am using the preloader class as a secondary thread to my main javafx application thread. If I used a Task, would it still be possible to run a javafx application thread, and do you possibly have any examples? Thanks : ) – Matias S Jan 02 '23 at 21:46
  • There is some (very dated and somewhat obsolete) information on [splash screens and JavaFX](https://stackoverflow.com/questions/15126210/how-to-use-javafx-preloader-with-stand-alone-application-in-eclipse), some is still relevant, but, in general, I'd recommend following DaveB's suggestion and Slaw's example. Also JavaFX 8 itself is obsolete, I'd recommend a modern version (e.g. 19+). – jewelsea Jan 03 '23 at 03:20

1 Answers1

5

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.

Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Wow! You got exactly what I wanted, and this solution works perfectly! Thanks! Moreover, can tasks run a JavaFX application thread? And what is wrong with using the model (Application) file as the controller as well? Again, many thanks : ) @Slaw – Matias S Jan 03 '23 at 04:27
  • 1
    A `Task` _can_ be executed on the FX thread, but that's not what they're designed for. The `call()` method is executed on the thread you give the task to, but note that the task's properties and things like the `onSucceeded` handler are executed on the FX thread. See the documentation of `javafx.concurrent` for more details. And note that you don't want stuff like networking code to be executed on the FX thread; I/O is pretty much the prime example of what should be given to background threads. Also, the `init()` method is not invoked on the FX thread, but the "JavaFX-Launcher" thread. – Slaw Jan 03 '23 at 06:17
  • 1
    As for the `Application` being used as an FXML controller as well, for one that simply violates the single-responsibility principle. Like I mentioned, the application class conceptually represents the entire application lifecycle. An FXML controller has no business being involved with that. At a more practical level, JavaFX creates an instance of the `Application` subclass for you (via reflection). This instance is managed by the framework. If it's also an FXML controller, then an `FXMLLoader` will create _another_ instance for itself. That makes things very hard to reason about. [cont.] – Slaw Jan 03 '23 at 06:19
  • Of course, that is only the default behavior of `FXMLLoader`, and you can configure it to use the same instance as the already-existing `Application` instance. But you should not do that, for reasons already mentioned. – Slaw Jan 03 '23 at 06:20
  • @MatiasS Advice: in future, considering asking further questions as questions rather than comments. Comments are kind of limiting and have horrible limits in length, formatting and search ability. You ask good questions and Slaw provided good answers. They can be more generally helpful to others in a Q&A format. – jewelsea Jan 03 '23 at 09:12
  • @Slaw: There's no indication that he's using FXML, and I'd guess that the "Controller" he's referring to is an MVC controller. While using Application as an FXML Controller is an horrific idea, it's probably somewhat less so to use it as an MVC Controller. In any event, the HTML connection stuff belongs in a Service accessed from the Model. – DaveB Jan 03 '23 at 16:45
  • @DaveB The indication is that the OP `implements Initializable`. That's not a 100% guarantee they are using FXML, since a lot of code (including the imports) is omitted, but in the context of a JavaFX application it's heavily implied. And yes, the OP should be using an architecture like MVC or MVVM, but I figured that was beyond the scope of this answer. – Slaw Jan 03 '23 at 21:24
  • Thanks for the feedback and answers. Thus, I assume by using a separate app, FXML, and controller files would follow an MVC architecture? Either way, your advice and wisdom has been much appreciated : ) – Matias S Jan 03 '23 at 22:48
  • @MatiasS In MVC, the model handles all the business logic (such as whether or not you can connect to a server). It knows nothing about the controller or the view. The view is, as expected, the visual representation of the model. And the controller handles connecting the view and model together and delegating user actions to the model. Within the scope of FXML, the view is the FXML file, the controller is the controller class (obviously), and the model comes from other code you've written (which, again, the model should ideally not even know the view or controller exist). – Slaw Jan 03 '23 at 23:08
  • 1
    @Slaw, I don't think that the FXML Controller counts as an MVC Controller. There's way, way, way too much coupling between the FXML file and the FXML Controller. In the same way, the FXML is not the independent entity that an MVC View needs to be, it's just the layout and it cannot have any knowledge of the Model (which it should do in MVC). At best, you can consider the FXML Controller to be an MVP Presenter, but that's a different framework. If using FXML with MVC, best to consider the FXML File, and FXML Controller together as the View. Then create an MVC Controller class. – DaveB Jan 04 '23 at 17:19
  • @MatiasS DaveB's comment brings up a good point. I recommend reading it if you haven't already. – Slaw Jan 07 '23 at 07:19