I am in the situation where I start a new view but in its controller i make a request to server to get some objects. This request takes some time so i want to implement a little loading view during this waiting time. So, in my controller i implemented a Service Object to make this request in another thread :
public void initialize(URL url, ResourceBundle resourceBundle) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/MiniPages/LoadingPage.fxml"));
Scene scene = new Scene(root);
Stage primaryStage = new Stage();
primaryStage.initStyle(StageStyle.UNDECORATED);
Service<ProjectModel> service = new Service<ProjectModel>() {
@Override
protected Task<ProjectModel> createTask() {
return new Task<ProjectModel>() {
@Override
protected ProjectModel call() throws Exception {
SenderText data = new SenderText();
int id = Integer.parseInt(data.getData());
Client client = Client.getInstance();
JSONObject tosend = new JSONObject();
tosend.put("Type", "Get Project");
tosend.put("IDproject", id);
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer());
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateDeserializer());
Gson gson = gsonBuilder.setPrettyPrinting().create();
client.sendText(tosend.toString());
String response = client.receiveText();
ProjectModel project = gson.fromJson(response, ProjectModel.class);
projectLocal = project;
System.out.println(gson.toJson(projectLocal));
primaryStage.close();
primaryStage.hide();
return project;
}
};
}
};
service.start();
primaryStage.setScene(scene);
primaryStage.initModality(Modality.APPLICATION_MODAL);
while (service.getValue() == null) {
primaryStage.showAndWait();
}
primaryStage.close();
System.out.println("Finish");
primaryStage.close();
} catch (Exception e) {
e.printStackTrace();
}
}
I tested and the object is set on call() method. But the loading view does not close. I also tried to call primaryStage.show()
instead of showandWait()
but it did not work. Any idea how to solve it?
>Solution :
There are two main threading rules in JavaFX (similar to most other UI toolkits).
- You must not perform operations on the UI from a background thread. This includes creating, showing, or hiding windows.
- You must not block the UI thread (the "FX Application Thread").
You violate the first rule by calling primaryStage.close()
in the call()
method.
You violate the second rule with your busy while
loop. (Worse; I think the service’s value
property is only updated on the FX Application Thread. So since you’re blocking that thread, the value
property can’t be updated and the while
loop never exits.)
To perform a UI action when the background Task
completes, you can use the onSucceeded
handler. This handler is invoked on the FX Application Thread, after the background task completes successfully.
public void initialize(URL url, ResourceBundle resourceBundle) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/MiniPages/LoadingPage.fxml"));
Scene scene = new Scene(root);
Stage primaryStage = new Stage();
primaryStage.initStyle(StageStyle.UNDECORATED);
Service<ProjectModel> service = new Service<ProjectModel>() {
@Override
protected Task<ProjectModel> createTask() {
return new Task<ProjectModel>() {
@Override
protected ProjectModel call() throws Exception {
SenderText data = new SenderText();
int id = Integer.parseInt(data.getData());
Client client = Client.getInstance();
JSONObject tosend = new JSONObject();
tosend.put("Type", "Get Project");
tosend.put("IDproject", id);
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer());
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateDeserializer());
Gson gson = gsonBuilder.setPrettyPrinting().create();
client.sendText(tosend.toString());
String response = client.receiveText();
ProjectModel project = gson.fromJson(response, ProjectModel.class);
return project;
}
};
}
};
service.setOnSucceeded(event -> {
System.out.println("Finish");
// I think; don't know what projectLocal is, or
// which threads it should be accessed from:
projectLocal = project ;
primaryStage.hide();
});
service.start();
primaryStage.setScene(scene);
primaryStage.showAndWait();
} catch (Exception e) {
e.printStackTrace();
}
}
As an aside, you probably don’t need a Service
here. Service
is really designed for repeated use, and you are only using it once. Simply creating a Task
and running it on a background thread should be enough:
public void initialize(URL url, ResourceBundle resourceBundle) {
try {
Parent root = FXMLLoader.load(getClass().getResource("/MiniPages/LoadingPage.fxml"));
Scene scene = new Scene(root);
Stage primaryStage = new Stage();
primaryStage.initStyle(StageStyle.UNDECORATED);
Task<ProjectModel> task = new Task<ProjectModel>() {
@Override
protected ProjectModel call() throws Exception {
SenderText data = new SenderText();
int id = Integer.parseInt(data.getData());
Client client = Client.getInstance();
JSONObject tosend = new JSONObject();
tosend.put("Type", "Get Project");
tosend.put("IDproject", id);
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateSerializer());
gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateDeserializer());
Gson gson = gsonBuilder.setPrettyPrinting().create();
client.sendText(tosend.toString());
String response = client.receiveText();
ProjectModel project = gson.fromJson(response, ProjectModel.class);
return project;
}
};
task.setOnSucceeded(event -> {
System.out.println("Finish");
// I think; don't know what projectLocal is, or
// which threads it should be accessed from:
projectLocal = project ;
primaryStage.hide();
});
new Thread(task).start();
primaryStage.setScene(scene);
primaryStage.showAndWait();
} catch (Exception e) {
e.printStackTrace();
}
}