meow meow - 2 months ago 6
Java Question

Java FX — No injectable field found in FXML Controller

EDIT: Solved: Tried to access fields before the controller variables were initialized.

I'm trying to use variable injection using @FXML annotation for ListView.

When I try to access the field in code I get an NPE:


Exception in Application constructor Exception in thread "main"
java.lang.RuntimeException: Unable to construct Application instance:
class ru.test.test.Main at
com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:907)
at
com.sun.javafx.application.LauncherImpl.lambda$launchApplication$152(LauncherImpl.java:182)
at
com.sun.javafx.application.LauncherImpl$$Lambda$2/2065951873.run(Unknown
Source) at java.lang.Thread.run(Thread.java:745) Caused by:
java.lang.reflect.InvocationTargetException at
sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at
sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at
com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$158(LauncherImpl.java:819)
at
com.sun.javafx.application.LauncherImpl$$Lambda$46/1246936109.run(Unknown
Source) at
com.sun.javafx.application.PlatformImpl.lambda$runAndWait$172(PlatformImpl.java:326)
at
com.sun.javafx.application.PlatformImpl$$Lambda$48/951810574.run(Unknown
Source) at
com.sun.javafx.application.PlatformImpl.lambda$null$170(PlatformImpl.java:295)
at
com.sun.javafx.application.PlatformImpl$$Lambda$50/1870495241.run(Unknown
Source) at java.security.AccessController.doPrivileged(Native Method)
at
com.sun.javafx.application.PlatformImpl.lambda$runLater$171(PlatformImpl.java:294)
at
com.sun.javafx.application.PlatformImpl$$Lambda$49/1193769192.run(Unknown
Source) 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$145(WinApplication.java:101)
at
com.sun.glass.ui.win.WinApplication$$Lambda$38/613256908.run(Unknown
Source) ... 1 more Caused by: java.lang.NullPointerException at
ru.test.test.Test.(Kursach.java:28) at
ru.test.test.Main.(Main.java:33) ... 18 more


I tried different access modifiers for my variable and as well I tried to access non-generic variables, like Button.

@FXML methods work nice without any problems. What's wrong with variables?

I have the following FXML file:



<?import javafx.scene.web.*?>
<?import javafx.scene.image.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.canvas.*?>
<?import java.lang.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<Pane focusTraversable="true" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="425.0" prefWidth="600.0" style="-fx-border-color: grey;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ru.test.kursach.Kursach">
<children>
<ListView id="pane_programPane_employeesList" fx:id="employeesList" layoutX="15.0" layoutY="43.0" prefHeight="200.0" prefWidth="200.0" />
<Button fx:id="test" layoutX="424.0" layoutY="259.0" mnemonicParsing="false" text="Редактировать">
<font>
<Font name="Arial" size="12.0" />
</font>
</Button>
</children>
</Pane>


Main class:

public class Main extends Application {

private static Scene scene = null;
private Kursach kursach = new Kursach();

@Override
public void start(final Stage stage) throws Exception {
stage.initStyle(StageStyle.UNDECORATED);
stage.getIcons().add(new Image(getClass().getResourceAsStream("resources/icon.png")));
Parent root = FXMLLoader.load(getClass().getResource("resources/kursach.fxml"));
stage.setScene(scene = new Scene(root));
Label titleLabel = (Label) getElement("#title");
final String title = titleLabel.getText() + " v. " + version;
titleLabel.setText(title);
stage.setTitle(title);
}

public static void main(String[] args) {
launch(args);
}
}


one more class:

public class Kursach {

@FXML private ListView<String> employeesList;
@FXML private Button test;

public Kursach() {
List<String> values = Arrays.asList("one", "two", "three");
//employeesList.setItems(FXCollections.observableList(values));
System.out.println(test.getText()); // NPE here
}

}

Answer

When the FXMLLoader load the FXML file, it

  1. Creates an instance of the class specified by the fx:controller attribute, by calling its no-argument constructor
  2. For any elements with a fx:id attribute, sets the value of a corresponding @FXML-annotated field in the controller instance to the object represented by that element
  3. Calls the initialize() method on the controller instance (if there is one).

Clearly, the constructor is invoked before the fields are injected (how could things possibly happen in any other order?). So if you try to access @FXML-injected fields in the constructor, they will be null. You need to access them in the initialize() method instead:

public class Kursach {

        @FXML private ListView<String> employeesList;
        @FXML private Button test;

        public void initialize() {
                List<String> values = Arrays.asList("one", "two", "three");
                //employeesList.setItems(FXCollections.observableList(values));
                System.out.println(test.getText()); // No NPE here
        }

}

Also note that the controller instance you create in your Application subclass with

private Kursach kursach = new Kursach();

is not the same instance that is created by the FXMLLoader, so it will not have its @FXML-annotated fields initialized (at any time). It doesn't matter in the code you posted, as you don't ever use it, but you should be aware of that.

Comments