Reputation: 13866
I'm trying to display a splash screen until I load all necessary resources and open the main stage afterwards, but I keep running into InvocationTargetException
.
In other words, my primary stage loads a FXML which has a Controller which looks like this:
public class SplashController {
@FXML
VBox splashScreenVBox = new VBox();
@FXML
protected void initialize() throws InterruptedException, IOException {
Stage primaryStage = (Stage) splashScreenVBox.getScene().getWindow();
primaryStage.close();
new MainStage();
}
}
The MainStage
class is just loading a FXML and showing the scene:
public MainStage() throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/core/views/Main.fxml"));
this.setScene(new Scene(root, 800, 600));
this.show();
}
The error that I'm getting points towards a line on which I FXMLLoader.load()
the FXML the first time (I have two FXML files, one for each stage).
Can someone shed some light on why this happens and preferably how to correctly work with the FXMLLoader
, in case that what I'm doing is an issue, please?
Edit: Stacktrace
Exception in Application start method
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$155(LauncherImpl.java:182)
at java.lang.Thread.run(Thread.java:745)
Caused by: javafx.fxml.LoadException:
/C:/Users/REDACTED/out/production/REDACTED/core/views/Splash.fxml
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2601)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2571)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2441)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3214)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3175)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3148)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3124)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3104)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3097)
at core.Scenes.start(Scenes.java:19)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$162(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$175(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
... 1 more
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2566)
... 17 more
Caused by: java.lang.NullPointerException
at core.controllers.SplashController.initialize(SplashController.java:18)
... 28 more
Exception running application core.Scenes
Upvotes: 1
Views: 414
Reputation: 45806
As you can see from the stack trace you are getting a NullPointerException
in the initialize
method of your SplashController
on line 18
. I'm going to assume that line 18
is this line:
Stage primaryStage = (Stage) splashScreenVBox.getScene().getWindow();
The issue is most likely this call: getScene().getWindow()
. The method getScene()
will return null
here because splashScreenVBox
is not part of a Scene
yet. How could it be? The initialize
method is called during the execution of FXMLLoader.load()
. This means you have not had the chance to add the result of FXMLLoader.load()
to a Scene
yet.
To fix this one option is adding a method to your SplashController
that does the loading for MainStage
.
FXMLLoader loader = new FXMLLoader(getClass().getResource("your/resource"));
Parent root = loader.load();
Stage splashStage = new Stage();
splashStage.setScene(new Scene(root));
splashStage.show();
SplashController controller = loader.getController();
controller.loadMainApp(splashStage);
I passed the splashStage
to the method so you can hide/close it when ready to show the main Stage
. Depending on the design of your code there may be other ways to do this than passing an argument.
An example of how to create an abstract controller that will automatically call a method when the root is added to a Scene
and that Scene
has been added to a Window
.
import java.util.function.Consumer;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.stage.Window;
public abstract class AbstractController<T extends Node> {
// protected so subclasses can access the root
// directly. You could also hide this behind a
// getter.
@FXML protected T root;
// Subclasses that override this method must call the
// super implementation
@FXML
protected void initialize() {
Consumer<Window> onNewWindow = this::onAddedToWindow;
Consumer<Scene> onNewScene = scene ->
scene.windowProperty().addListener(new SelfRemovingChangeListener<>(onNewWindow));
root.sceneProperty().addListener(new SelfRemovingChangeListener<>(onNewScene));
}
protected abstract void onAddedToWindow(Window window);
private static class SelfRemovingChangeListener<T> implements ChangeListener<T> {
private final Consumer<? super T> onNewValue;
private SelfRemovingChangeListener(Consumer<? super T> onNewValue) {
this.onNewValue = onNewValue;
}
@Override
public void changed(ObservableValue<? extends T> observable, T oldValue, T newValue) {
onNewValue.accept(newValue);
observable.removeListener(this);
}
}
}
This requires that you have a fx:id="root"
in your FXML file and that the root object is a Node
. The onAddedToWindow
is only called the first time the root
has been added to a Window
. You still have to add the root
to a Window
elsewhere (wherever you call FXMLLoader.load
).
Upvotes: 1