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}")
    }
}
GregInWI2
  • 904
  • 1
  • 17
  • 26
  • I don't know Kotlin: is the `init` method called as part of object construction? – James_D Jul 17 '16 at 23:50
  • 2
    You should check out [TornadoFX](https://github.com/edvin/tornadofx). It makes JavaFX in Kotlin really nice, and has type safe DSLs for both the layout and the styles. – Ruckus T-Boom Jul 19 '16 at 17:14
  • 2
    To add to @RuckusT-Boom's comment, it also supports FXML and streamlines it quite nicely. https://github.com/edvin/tornadofx-guide/blob/master/Part%202%20-%20Usage%20Basics/10.%20FXML.md – tmn Jul 21 '16 at 05:46

2 Answers2

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
}
Payne
  • 456
  • 6
  • 21
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}")
    }
}
Marv
  • 3,517
  • 2
  • 22
  • 47
  • 6
    Note that you don't actually need to implement `Initializable`; simply defining an `initialize()` method is enough. – James_D Jul 17 '16 at 23:55
  • Yep, that did it! Awesome. I did it the shorter way without the override. – GregInWI2 Jul 18 '16 at 00:01
  • 1
    Thats the recommended way as per the [JavaFX documentation](https://docs.oracle.com/javafx/2/api/javafx/fxml/Initializable.html) 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. – Marv Jul 18 '16 at 00:02