App hangs up or "Not on FX application thread" occurs during app activity
The application reacts on actions which occur on gamepad. When button is pressed something happens on UI. But I ran at the issue with app hangs up or "java.lang.IllegalStateException: Not on FX application thread" exception.
In order to fix it I tried the following approaches: Platform.runLater()
and Task
usage. But it didn't help.
Here is the problem code:
public class GamepadUI extends Application{
private static final int WIDTH = 300;
private static final int HEIGHT = 213;
private Pane root = new Pane();
private ImageView iv1 = new ImageView();
private boolean isXPressed = false;
@Override
public void start(Stage stage) throws Exception {
initGUI(root);
Scene scene = new Scene(root, WIDTH, HEIGHT);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
}
public void pressBtn() {
if(!isXPressed) {
iv1.setVisible(true);
isXPressed = true;
}
}
public void releaseBtn() {
if(isXPressed) {
iv1.setVisible(false);
isXPressed = false;
}
}
private void initGUI(final Pane root) {
Image image = new Image(Props.BUTTON);
iv1.setImage(image);
iv1.setLayoutX(198);
iv1.setLayoutY(48);
iv1.setVisible(false);
root.getChildren().add(iv1);
runTask();
}
public void runTask() {
Task task = new Task<Void>() {
@Override
protected Void call() throws Exception {
initStubGamepad();
return null;
}
};
new Thread(task).start();
}
public static void main(String[] args) {
launch(args);
}
public void initStubGamepad() {
Random rnd = new Random();
try {
while (true) {
if (rnd.nextInt(30) == 3) {
pressBtn();
} else if (rnd.nextInt(30) == 7) {
releaseBtn();
}
}
} catch (Exception ex) {
System.out.println("Exception: " + ex);
}
}
}
initStubGamepad()
emulates gamepad buttons activity polling. When user presses any button (rnd.nextInt(30) == 3
) - an image appears on the UI. When user releases that button (rnd.nextInt(30) == 7
) - an image disappears from the UI.
In case above java.lang.IllegalStateException: Not on FX application thread
occurs. If you change runTask() to something like this:
Platform.runLater(new Runnable() {
@Override
public void run() {
initStubGamepad();
}
});
Then app will hang or even main UI won't appear at all, but gamepad activity continues.
What I want is just to show/hide different images when some activity is detected on gamepad (btw, there's no way to monitor gamepad activity except for gamepad polling in an infinite loop). What did I wrong
Solution 1:
Explanation
In the first scenario, when you are using
Task task = new Task<Void>() {
@Override
protected Void call() throws Exception {
initStubGamepad();
return null;
}
}
Inside initStubGamepad()
, which is running on a Task, you are trying to update the UI components inside pressBtn()
and releaseBtn()
methods, which is why you are facing a
java.lang.IllegalStateException: Not on FX application thread
because all the UI updates must occur on the JavaFX thread
In the second scenario, when you are using
Platform.runLater(new Runnable() {
@Override
public void run() {
initStubGamepad();
}
});
the UI doesnt appear, because you have an infinite loop inside the initStubGamepad()
, which puts the JavaFX application thread run on an infinite loop
Solution
By the time you have reach here, you must have already found the solution. In case you haven't, try try to put the update the Javafx components on the UI thread. So, instead of calling initStubGamepad()
inside Platform.runLater
, try calling pressBtn()
and releaseBtn()
inside it.
Try using
while (true) {
if (rnd.nextInt(30) == 3) {
Platform.runLater(() -> pressBtn());
} else if (rnd.nextInt(30) == 7) {
Platform.runLater(() -> releaseBtn());
}
}
or you may also use
public void pressBtn() {
if(!isXPressed) {
Platform.runLater(() -> iv1.setVisible(true));
isXPressed = true;
}
}