Kotlin and JavaFX

I've been experimenting a little with Kotlin over the last few days. Using JavaFX with Kotlin was one of the main topics. This article covers what I've learned.

Introduction

At the point of writing this article, Kotlin is a rather new language. What I personally like in Kotlin is the clean combination of functional and object-oriented programming. The interaction with Java (so far my main language) is very smooth as well. Being able to use Java resources without any special syntax (including class/interface extension) makes this interaction feel quite natural.

JavaFX on the other hand has been around for quite a few years now. It allows for GUI creation in code, similar to Swing. It also allows for GUI creation by using FXML for GUI structure, a CSS dialect for styling and code for loading the UI and event handling.

Before the article starts, I want to mention that this is not the first work on this topic. There is TornadoFX which is a framework for combining Kotlin and JavaFX. I'm not going to use or evaluate it in this article. My personal experience with JavaFX is that it is simple enough to use without any framework. Hence, I don't use any framework here except for testing.

Project and setup

For this little project I use the following:

  • Kotlin as programming language
  • JavaFX for GUI development
  • JUnit and TestFX for testing

I call the project kotlinfx. In this project, the application consists of a simple JavaFX UI with a label, a text field and a button. The UI backend is a single controller and there is some code to start the application. Clicking the button will update the label with the text that is in the text field. This example is almost the same as in this linked article from Uzair Shamim. The differences are a few UI elements and I use Kotlin instead of Java. I choose to use the same example for easy comparison of the Kotlin and the Java implementation via Uzair Shamims and my article.

Use this link to find the complete sources of my kotlinfx project on Github.

Creating a JavaFX app with Kotlin

I decided to split the application into six files:

  • Main.kt
  • MyView.kt
  • LoadApp.kt
  • MyController.kt
  • my-view.fxml
  • my-view.css

Main.kt contains the main function to run the application. The difference to Java is that only a stand-alone function is required instead of a main class. To create a JavaFX GUI it is required to implement javafx.application.Application (for the rest of the article: Application). MyView is exactly this implementation. MyController is the controller that contains the logic for the view. The initialization of the GUI is extracted into the function loadApp. my-view.fxml contains the view structure and my-view.css the styling.

my-view.fxml
<VBox xmlns="http://javafx.com/javafx"
    xmlns:fx="http://javafx.com/fxml"
    fx:controller="de.atennert.kotlinfx.MyController"
    prefHeight="400.0" prefWidth="600.0">
  <stylesheets>
    <String fx:value="my-view.css" />
  </stylesheets>

  <Label text="Hello World" fx:id="myLabel"/>
  <TextField fx:id="myText"/>

  <Button fx:id="myButton" text="Update label" onAction="#updateLabel"/>
</VBox>

As said before, the view consists of a label, a text field and a button. my-view.fxml is a standard JavaFX FXML file. There is nothing special so far.

Main.kt
fun main() {
    // Start the UI
    MyView.run()
}
MyView.kt
class MyView : Application() {
    override fun start(primaryStage: Stage?) {
        loadApp(primaryStage)
    }

    companion object {
        /** Runs the de.atennert.kotlinfx.main view. This method is blocking! */
        fun run(): Unit = launch(MyView::class.java)
    }
}

This is the most complicated part of the project. Usually the implementation of Application (here MyView) is also the main class. This doesn't work in Kotlin, because it uses the main function. Hence, the launch method of Application must be called by the main method in Main.kt. Unfortunately the static launch method is not accessible from outside the Application class. The solution is to define a proxy function in a so called companion object inside MyView to call launch. A companion object allows to define functions that work like static methods in a Java class. I called this function run. run can be called statically from outside MyView which is done by the main function.

Note how MyView extends Application just like it would extend another Kotlin class.

The function start usually contains all the code to set up the GUI. This code is extracted into loadApp as shown in the next figure. I decided to do this in order to test the code, but more of that later.

LoadApp.kt
fun loadApp(stage: Stage?) {
    val fxmlLoader = FXMLLoader(ClassLoader.getSystemResource("my-view.fxml"))
    val pane = fxmlLoader.load()

    val scene = Scene(pane)

    stage?.title = "MyApp"
    stage?.scene = scene

    stage?.show()
}

This is almost the same code as in Java, except for the ? which are used to check wether the object is null (stage?.title = ...). In terms of the parameter type, ? states that stage might be null. What's the content of the function? First, the FXML is loaded and put into a Scene. Then the Stage is configured and shown. With those few lines, the JavaFX UI will be visible on the screen.

MyController.kt
class MyController {

    @FXML
    private lateinit var myLabel: Label

    @FXML
    private lateinit var myText: TextField

    fun updateLabel(event: ActionEvent) {
        myLabel.text = myText.text
    }
}

This looks very much like the Java version as well. @FXML is the usual Java annotation.

What is different: Kotlin is hiding getters and setters so Java's myLabel.setText( myText.getText() ); becomes myLabel.text = myText.text in Kotlin. Secondly, lateinit is required to tell Kotlin that the content in the fields will be assigned later. This works even with marking them private as they are assigned by JavaFX using reflection. There is other ways then using lateinit, but to me this looks like a cleaner solution compared to p.ex. assigning null initially and setting the types to Label? and TextField?.

The application doesn't look nice at all as the following picture shows, but for this evaluation I don't care for layout, I care for a technical example. The only styling is changing the label background to green. This is only to include some CSS.

The GUI of the application
The application GUI

Testing the JavaFX application

The tests for this Kotlin JavaFX application are written with JUnit 5 and TestFX. TestFX is useful because it manages the creation, reseting and shutdown of the test Application instance. TestFX also provides tools to access, use and check the GUI elements and their properties.

Note: There are different versions of TestFX for JUnit 4 and JUnit 5.

Note: TestFX uses a special test implementation of Application. Hence, the class that extends Application (here MyView) is not tested!

UITest.kt
class UITest : ApplicationTest() {
    override fun start(stage: Stage?) {
        loadApp(stage)
        stage?.toFront()
    }

    @BeforeEach
    fun setup() {
    }

    @AfterEach
    fun tearDown() {
        FxToolkit.hideStage()
    }

    @Test
    fun `check that button exists`()
            = FxAssert.verifyThat<Button>("#myButton") {it != null}

    @Test
    fun `check initial label text`()
            = FxAssert.verifyThat("#myLabel", LabeledMatchers.hasText("Hello World"))

    @Test
    fun `change label text`() {
        val testString = "test"

        clickOn("#myText")
        write(testString)
        clickOn("#myButton")

        FxAssert.verifyThat("#myLabel", LabeledMatchers.hasText(testString))
    }
}

For implementing JavaFX tests with TestFX, it is necessary to extend the class ApplicationTest. ApplicationTest provides the test implementation of Application as well as some additional tooling. It is necessary to override the start method to setup the GUI, similar to MyView. This can be done by calling loadApp the same way as in MyView. This is also the reason why the setup code is extraced into this function. It becomes part of the tested code. After loadApp, stage?.toFront() is called to make sure that the GUI is accessible to the mouse pointer. This is done because TestFX interacts with the GUI by controlling the mouse.

There are three example test methods that check:

  • the existance of the button using a Predicate,
  • the initial text of the label using a label matcher and
  • the label text after entering a text in the TextField and pressing the button.

The first thing to note is that Kotlin allows spaces in function names. The coding conventions only allow this in tests and here it really helps with readability. The second thing is that all checks are done with FxAssert. FxAssert offers two methods: verifyThat and verifyThatIter. Additional utility methods are part of FxRobot which is extended by ApplicationTest. This inlcudes functions for manipulating and querying GUI elements. Those methods will be part of the test class because it extends ApplicationTest.

The first test uses Javas functional interface Predicate. In Java this can be written as a lambda and so it can in Kotlin. Kotlin has a special keyword for use in lambdas that have only one parameter: it. The part of a lambda before and including the arrow is removed when using it. The alternative code would be {btn: Button? -> btn != null}. verifyThat is generic, so Button needs to be declared as generic type after the method to ensure correct typing when using it. The type can be deduced when using the longer lambda with parameter type.

The second test case looks similar to the first. The difference is that LabeledMatchers is used to check the text of the Label element. This matcher works for any element that extends from the Labeled class. TestFX offers a variety of matchers for different use-cases.

The third test uses some of FxRobots utility methods to select the text field, enter some text and click the button. After this sequence, the matcher is used again on the label to check for the update of the text. The GUI commands are written one after another in seperate calls. This makes it easy to read and understand. However, the methods for GUI manipulation return the interface of FxRobot: FxRobotInterface. Hence, it is also possible to chain the commands like this:

Alternative commands for GUI manipulation
clickOn("#myText")
    .write(testString)
    .clickOn("#myButton")

Summary

Besides the differences with the main function and the launch method, everything around JavaFX in Kotlin looks and works very much like in Java. This project was definitely a lot easier to create then using FregeFX with FXML and a controller class (see this article).

I used IntelliJ to create this project and the IDE support for JavaFX and Kotlin is as good as for JavaFX and Java from what I can see. For instance, the IDE is able to make the connection between the FXML objects and the fields and functions in the controller which helps with navigating and refactoring.