JavaFX FXML controller - constructor vs initialize method
Asked Answered
A

3

114

My Application class looks like this:

public class Test extends Application {

    private static Logger logger = LogManager.getRootLogger();

    @Override
    public void start(Stage primaryStage) throws Exception {

        String resourcePath = "/resources/fxml/MainView.fxml";
        URL location = getClass().getResource(resourcePath);
        FXMLLoader fxmlLoader = new FXMLLoader(location);

        Scene scene = new Scene(fxmlLoader.load(), 500, 500);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

The FXMLLoader creates an instance of the corresponding controller (given in the FXML file via fx:controller) by invoking first the default constructor and then the initialize method:

public class MainViewController {

    public MainViewController() {
        System.out.println("first");
    }

    @FXML
    public void initialize() {
        System.out.println("second");
    }
}

The output is:

first
second

So, why does the initialize method exist? What is the difference between using a constructor or the initialize method to initialize the controller required things?

Thanks for your suggestions!

Acceptance answered 14/1, 2016 at 9:6 Comment(0)
R
170

In a few words: The constructor is called first, then any @FXML annotated fields are populated, then initialize() is called.

This means the constructor does not have access to @FXML fields referring to components defined in the .fxml file, while initialize() does have access to them.

Quoting from the Introduction to FXML:

[...] the controller can define an initialize() method, which will be called once on an implementing controller when the contents of its associated document have been completely loaded [...] This allows the implementing class to perform any necessary post-processing on the content.

Roundtheclock answered 14/1, 2016 at 9:21 Comment(3)
I don't understand. The way he does it is over the FXMLLoader, right? So I don't see a benefit in waiting for the initialize() - method. As soon as the FXML is loaded, the following code has access to the @FXML variables. Sure, he does it in the start method and not in the constructor, but would initialize() bring any benefit in his case?Ilanailangilang
The point is that a constructor can't use instance variables that are injected by the container. It's a chicken-egg problem. So the flow is construction -> injection -> initialization. This is a ubiquitous idiom for IOC frameworks in many languages. The constructor is responsible for allocation memory and ensuring a valid initial state for the instance. The initializer adds behaviour that operates on the post-injected state. This is more flexible than constructor injection.Verdun
Example injected state would be references to subviews and controls that are created by the FXML loader. Configuring attributes of those subviews programmatically would be impossible during construction - due to possible instance caching, bidirectional references, order of instantiation restrictions, etc - so you instead are given the initialize() method in which to do that work with the guarantee that all the injected references are non-null and initialized. Cocoa uses a similar approach with viewDidLoad().Verdun
E
103

The initialize method is called after all @FXML annotated members have been injected. Suppose you have a table view you want to populate with data:

class MyController { 
    @FXML
    TableView<MyModel> tableView; 

    public MyController() {
        tableView.getItems().addAll(getDataFromSource()); // results in NullPointerException, as tableView is null at this point. 
    }

    @FXML
    public void initialize() {
        tableView.getItems().addAll(getDataFromSource()); // Perfectly Ok here, as FXMLLoader already populated all @FXML annotated members. 
    }
}
Ephraim answered 14/1, 2016 at 9:20 Comment(0)
H
18

In Addition to the above answers, there probably should be noted that there is a legacy way to implement the initialization. There is an interface called Initializable from the fxml library.

import javafx.fxml.Initializable;

class MyController implements Initializable {
    @FXML private TableView<MyModel> tableView;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        tableView.getItems().addAll(getDataFromSource());
    }
}

Parameters:

location - The location used to resolve relative paths for the root object, or null if the location is not known.
resources - The resources used to localize the root object, or null if the root object was not localized. 

And the note of the docs why the simple way of using @FXML public void initialize() works:

NOTE This interface has been superseded by automatic injection of location and resources properties into the controller. FXMLLoader will now automatically call any suitably annotated no-arg initialize() method defined by the controller. It is recommended that the injection approach be used whenever possible.

Halfhearted answered 27/12, 2018 at 16:27 Comment(2)
The no-args initialize() method doesn't even have to be annotated with @FXMLBac
@Bac That's only true if it's public. You can make the initialize() method (either no-args, or with the same arguments as the one in the interface) private, and annotate it @FXML, which can enhance encapsulation.Apgar

© 2022 - 2024 — McMap. All rights reserved.