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?
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();
}
// ...
}
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!"));
}
}
Your Application
subclass should not implement Initializable
. That subclass represents the entire application and should never be used as an FXML controller.