Search code examples
javajavafxnullpointerexceptionfxmlfxmlloader

FXML variable reference is null


So I'm learning how to use FXMl and I have run into yet another problem with getting a reference to an object by fx:id at runtime, I get a null pointer exception with the following code.

  <TableView fx:id="productTable" layoutX="92.0" layoutY="319.0" prefHeight="97.0" prefWidth="363.0" GridPane.columnIndex="7" GridPane.columnSpan="5" GridPane.rowIndex="3" GridPane.rowSpan="2">
     <columns>
        <TableColumn prefWidth="98.0" text="Product ID" fx:id="productID"/>
        <TableColumn prefWidth="110.0" text="Product Name" fx:id="productName"/>
        <TableColumn prefWidth="131.0" text="Inventory Level" fx:id="productInventoryLevel"/>
        <TableColumn prefWidth="128.0" text="Price per Unit" fx:id="productPrice"/>
     </columns>
  </TableView>
//Controller.java
@FXML private TableView<Product> productTable;
@FXML private TableColumn<Product, String> productID;
@FXML private TableColumn<Product, String>  productPrice;
@FXML private TableColumn<Product, String>  productName;
@FXML private TableColumn<Product, String>  productInventoryLevel;

//Product properties declared the same as https://docs.oracle.com/javafx/2/ui_controls/table-view.htm#
private ObservableList<Product> productData = FXCollections.observableArrayList(
        new Product(0, "Wheel", 100.99, 4, 0, 1), 
        new Product(1, "Seat", 50.0, 4, 0, 1));

@Override
public void initialize(URL url, ResourceBundle rb) {
    // TODO
    System.out.println("Product ID factories");

    productID.setCellValueFactory(new PropertyValueFactory<Product, String>("productID"));
    productPrice.setCellValueFactory(new PropertyValueFactory<Product, String>("productPrice"));
    productInventoryLevel.setCellValueFactory(new PropertyValueFactory<Product, String>("productInventoryLevel"));
    productName.setCellValueFactory(new PropertyValueFactory<Product, String>("productName"));
     productTable.setItems(productData);

      System.out.println("Set items");

}    

As you can see I declare the @FXML tag, then the variable by fx:id and right after my first print statement I get a runtime nullpointer exception on the productID

EDIT: The above code currently runs without error but will only populate the "productID" section of my table.


Solution

  • Firstly, it's not sure what the UI class extends. It does look like it extended from Application, and is the starting class of the whole application.

    Making my best guess, I would say there are two possible reasons why you did this:

    1. You copied from the first (Application extending) class. This is unlikely since you said you had a NullPointerException, unless you manually called start().
    2. This "controller" class is indeed the starting class, and you intended to let it be the controller class for FXMLDocument.fxml. This seems to be the likely case here.

    Assuming that you want the Application extending class to be a controller, you need to create an instance of FXMLLoader, instead of using the static method FXMLLoader.load().

    FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
    loader.setController(this); // You need to set this instance as the controller.
    Parent root = loader.load();
    // Other stuff you need to do
    productID.setCellValueFactory(new PropertyValueFactory<Product, String>("productID"));
    

    This will set the currently running instance of UI as the controller for FXMLDocument.fxml.

    There are several points to take note.

    1. Do not use fx:controller in FXML, or create a new instance of UI (and setting it) as an alternative. You must provide the current instance to the FXMLLoader.
    2. All fields annotated via @FXML will only be injected (i.e. non-null) after FXMLLoader.load() has been called, or in initialize() method. If you try to call productID.setCellValueFactory(new PropertyValueFactory<Product, String>("productID"));, you may get a NullPointerException, depending on the sequence of your codes.

    Lastly, you do have an option of making another class the controller of the FXML file. In fact, most people would do that as that is the purpose of MVC (which is the architecture JavaFX uses). Of course, all injected fields and initialization would also move to the new controller class. Also remember that injected fields are null in constructor - so initialize in initialize() of the new controller class.