FXML control always null when using Kotlin
Asked Answered
S

2

8

Using IntelliJ I created a JavaFX application and then added Kotlin and Maven as frameworks to it. It came with a sample.fxml file and a Controller.java and Main.java. I created a new class for the controller in Kotlin (MainWindowController.kt) and renamed the sample.fxml file to MainWindow.fxml. I updated the MainWindow.fxml to look like:

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:controller="reader.MainWindowController" xmlns:fx="http://javafx.com/fxml" xmlns="http://javafx.com/javafx/8" alignment="center" hgap="10" vgap="10">
    <Label fx:id="helloLabel" text="Hello"/>
</GridPane>

And in my MainWindowController.kt file I have:

package reader

import javafx.fxml.FXML
import javafx.scene.control.Label

class MainWindowController {

    @FXML var helloLabel: Label? = null

    init {
        println("Label is null? ${helloLabel == null}")
    }
}

Here's my Main.java:

import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getClassLoader().getResource("MainWindow.fxml"));
        primaryStage.setTitle("My App");
        primaryStage.setScene(new Scene(root, 1000, 600));
        primaryStage.show();
    }

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

When I run the app the print line shows that the label is null but otherwise the window shows properly and I see the text from my label. The null is the problem I'm having. I haven't found much on using FXML with Kotlin and what I did find was a little out-dated and didn't appear to have an actual working solution.

Does anyone know why the label is null? I must be doing something wrong or misunderstanding something.

Edit: Here's what I have that now works thanks to the quick replies:

package reader

import javafx.fxml.FXML
import javafx.scene.control.Label

class MainWindowController {

    @FXML var helloLabel: Label? = null

    fun initialize() {
        println("Label is null? ${helloLabel == null}")
    }
}
Stavropol answered 17/7, 2016 at 23:45 Comment(3)
I don't know Kotlin: is the init method called as part of object construction?Torhert
You should check out TornadoFX. It makes JavaFX in Kotlin really nice, and has type safe DSLs for both the layout and the styles.Illumination
To add to @RuckusT-Boom's comment, it also supports FXML and streamlines it quite nicely. github.com/edvin/tornadofx-guide/blob/master/…Nellienellir
H
6

Just like with Java constructors, fx:id fields will not be populated before but after the init (or in Java the constructor) is called. A common solution is to implement the Initializable interface (or just define an initialize() method) and do additional setup inside the method like so:

import javafx.fxml.FXML
import javafx.scene.control.Label

class MainWindowController : Initializable {
    @FXML 
    var helloLabel: Label? = null

    override fun initialize(location: URL?, resources: ResourceBundle?) {
        println("Label is null? ${helloLabel == null}")
    }
}
Hydrophobic answered 17/7, 2016 at 23:54 Comment(3)
Note that you don't actually need to implement Initializable; simply defining an initialize() method is enough.Torhert
Yep, that did it! Awesome. I did it the shorter way without the override.Stavropol
Thats the recommended way as per the JavaFX documentation anyways: 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.Hydrophobic
W
16

As mentioned before. Check if fx:id is set.

Is also possible to use lateinit modifier.

Your code could looks like:

import javafx.fxml.FXML
import javafx.scene.control.Label

class MainWindowController {
    @FXML 
    lateinit var helloLabel : Label
}
Wadi answered 7/7, 2017 at 15:30 Comment(0)
H
6

Just like with Java constructors, fx:id fields will not be populated before but after the init (or in Java the constructor) is called. A common solution is to implement the Initializable interface (or just define an initialize() method) and do additional setup inside the method like so:

import javafx.fxml.FXML
import javafx.scene.control.Label

class MainWindowController : Initializable {
    @FXML 
    var helloLabel: Label? = null

    override fun initialize(location: URL?, resources: ResourceBundle?) {
        println("Label is null? ${helloLabel == null}")
    }
}
Hydrophobic answered 17/7, 2016 at 23:54 Comment(3)
Note that you don't actually need to implement Initializable; simply defining an initialize() method is enough.Torhert
Yep, that did it! Awesome. I did it the shorter way without the override.Stavropol
Thats the recommended way as per the JavaFX documentation anyways: 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.Hydrophobic

© 2022 - 2024 — McMap. All rights reserved.