FXML: "Run after initialized"

java, javafx

Solution

If you specify the controller in the FXML file with `fx:controller="Controller"`, then when you call `FXMLLoader.load(...)`, the `FXMLLoader`:

- parses the FXML file

- creates an instance of `Controller` by (effectively) calling its no-arg constructor (or, in advanced usage, by invoking the controller factory if you set one)

- creates the UI elements corresponding to the elements in the FXML file

- injects any elements with an `fx:id` into matching fields in the controller instance

- registers event handlers

- invokes `initalize()` on the controller instance (if such a method is defined)

- returns the UI element corresponding to the root of the FXML hierarchy

Only after `load()` completes (i.e. after the `@FXML`-annotated fields are injected) can you get a reference to the controller with `loader.getController()`. So it is not possible (aside from doing something extremely unusual in a controller factory implementation) for you to invoke any methods on the controller instance until after the `@FXML`-injected fields are initialized. Your null checks here are redundant.

On the other hand, if you use `FXMLLoader.setController(...)` to initialize your controller, in which case you must not use `fx:controller`, you can pass the values to the constructor. Simply avoiding calling a `set` method on the controller before passing the controller to the `FXMLLoader` means you can assume any `@FXML`-annotated fields are initialized in the controller's public methods:

class Controller{
    @FXML
    Pane aPane;
    int globalValue;

    public Controller(int globalValue) {
        this.globalValue = globalValue ;
    }

    public Controller() {
        this(0);
    }

    public void setSomething(int value){
        globalValue = value;
        aPane.someMethod(globalValue)
    }

    @FXML
    void initialize(){
        aPane.someMethod(globalValue) //guaranteed not null at this point
    }


}

and

FXMLLoader loader = new FXMLLoader(getClass().getResource("path/to/fxml"));
Controller controller = new Controller(42);
loader.setController(controller);
Node root = loader.load();

Problem

I have a JavaFX application that uses FXML alongside a controller class written in Java. In the Java controller I need to take care not to operate on an FXML `Node` element until it's been initialized (otherwise I'll get a NullPointerException), which isn't guaranteed until the `initialize` method is run. So I find myself doing this a lot: The controller is set in the FXML file like this: ``` <Pane fx:controller="Controller" ...> ... </Pane> ``` And then here's the controller in the Java file. ``` class Controller{ @FXML Pane aPane; int globalValue; public void setSomething(int value){ globalValue = value; if(!(aPane == null)){ //possibly null if node not initialized yet aPane.someMethod(globalValue) } } @FXML void initialize(){ aPane.someMethod(globalValue) //guaranteed not null at this point } } ``` This works, but it's clunky and repetitive. I have to create the `globalValue` attribute just in case the `setSomething` method is called before `initialize` has been called, and I have to make sure the operations in my `setSomething` method are identical to the operations in `initialize`. Surely there's a more elegant way to do this. I know that JavaFX has the `Platform.runlater(...)` method that guarantees something will be run on the main application thread. Perhpas there's something like `Platform.runAfterInitialize(...)` that waits until initialization, or runs immediately if initialization already happened? Or if there's another way to do it I'm open to suggestions.

Original source