Writing JavaFX applications with Kotlin

Hi there! Today I'll like to show you how Kotlin can be used to write a JavaFX application. This particular example encompasses both runtime and buildtime aspects as I'll also show how Gradle Kotlin Script can be used to build the project. Let's get started!

UPDATE: The code assumes you're using Oracle JDK8 which includes JavaFX (jfxrt.jar) by default. Make sure you have a compatible JavaFX binary if you're using a different JDK release, such as OpenJDK. You can install OpenJFX on Ubuntu by invoking sudo apt-get install openjfx for example.

Update 2: You may encounter problems when attempting to run the application from within IntelliJ. This is due to an open bug (https://youtrack.jetbrains.com/issue/KT-15040) that prevents IntelliJ from generating appropriate outputs when kapt is in place. Alternatives are to build and run the application outside of the IDE or configure the IDE to delegate the build steps to Gradle (see here).

I've selected Griffon as my framework of choice for obvious reasons:

  • There's a series of well-documented steps to get started with Griffon, even with Kotlin.
  • Griffon separates application logic (Controller/Service) from the UI (View) and the data (Model) used by the two others to communicate.

Disclaimer: I'm one of the founders and current lead of the Griffon framework. This being said, we could create the application by following the steps laid out in the Griffon tutorial, however the current version of the griffon-javafx-template does not support Gradle Kotlin Script just yet; what we'll do instead is create the project from scratch as explained here, which goes to show that Griffon projects are nothing more than regular Gradle projects (or even Maven). And just in case, all the code we'll see in this post is readily available on GitHub. The application should look like this once we're finished

Figure 1. Hello Kotlin from Griffon

First, make sure you have the latest Gradle 4.x installed on your system (at the time of writing it's 4.0-rc-2). You can install it easily by using SDKMAN. Out next stop would be to create the basic project layout and a starting Gradle Kotlin Script file, such as

$ mkdir kotlin
$ cd kotlin
$ touch build.gradle.kts
$ touch gradle.properties
$ mkdir -p src/main/kotlin src/main/resources/
$ gradle wrapper

Which results in the following structure being created

.
├── build.gradle.kts
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── src
    └── main
        ├── kotlin
        └── resources

Looking good so far. Let's define some basic information for our project, such as group, version, and other properties. We can add these properties to build.gradle.kts however I prefer to keep them outside of the build file itself, for reasons explained at [Issue 001] Gradle project properties (the first issue of my newsletter, subscriber welcome!) Here's how gradle.properties should look like

group          = org.example
version        = 0.1.0-SNAPSHOT
griffonVersion = 2.11.0
slf4jVersion   = 1.7.25
kotlinVersion  = 1.1.2-2

org.gradle.script.lang.kotlin.accessors.auto = true

The file defines the Griffon version to be 2.11.0 (latest release) and Kotlin to be 1.1.2-2 (not the latest release). The reason for the latter to be an slightly older version is that the kotlin-kapt plugin (which we'll use in just a minute) does not follow the latest Kotlin release (1.1.2-4 at the moment). Without any further ado, here is the build file in all its glory

val griffonVersion by project
val kotlinVersion by project
val slf4jVersion by project

apply {
    plugin("kotlin")
}

plugins {
    id("org.jetbrains.kotlin.kapt") version "1.1.2-2"
    application
}

application {
    mainClassName = "sample.javafx.kotlin.LauncherKt"
}

repositories {
    jcenter()
    gradleScriptKotlin()
}

dependencies {
    kapt("org.codehaus.griffon:griffon-core-compile:$griffonVersion")
    compileOnly("org.codehaus.griffon:griffon-core-compile:$griffonVersion")

    compile("org.codehaus.griffon:griffon-javafx:$griffonVersion")
    compile("org.codehaus.griffon:griffon-guice:$griffonVersion")
    compile(kotlinModule("stdlib"))

    runtime("org.slf4j:slf4j-simple:$slf4jVersion")

    testCompile("org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion")
    testCompile("org.codehaus.griffon:griffon-core-test:$griffonVersion")
    testCompile("org.codehaus.griffon:griffon-javafx-test:$griffonVersion")
}

tasks.withType<ProcessResources> {
    filesMatching("**/*.properties") {
        expand(hashMapOf(
                "application_name"    to project.name,
                "application_version" to project.version,
                "griffon_version"     to griffonVersion
        ))
    }
}

From the top, we resolve some of the properties defined in gradle.properties as immutable variables as we'll use them in the script. Next we apply the kotlin, kotlin-kapt, and application plugins. The main entry point for the application is defined to be sample.javafx.kotlin.LauncherKt. This type is actually a Kotlin script. I must confess I was thrown off at first by figuring out the name of the actual type as the file name is sample/javafx/kotlin/Launcher.kt; there's a naming convention followed by the Kotlin compiler; once you become aware of it the pieces fall into place. The next block defines artifact repositories, followed by the actual dependencies we need for this application. This is where the versions we resolved earlier come into play. Finally the build copies resources while applying a filter to a particular set of placeholders. Alright, with the build file done we can turn our attention to the code.

As mentioned before, Griffon separates responsibilities following the MVC pattern (but it can also do other variations) which will result in a set of classes where the responsibility of each class is well defined. Following the MVC pattern we'll end up with a Model, a View, and a Controller. We can go a bit further and split the login into Controller and Service, showing the Dependency Injection capabilities of the framework. We end up with the following updated structure

.
├── build.gradle.kts
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── src
    └── main
        ├── kotlin
        │   └── sample
        │       └── javafx
        │           └── kotlin
        │               ├── ApplicationModule.kt
        │               ├── Config.kt
        │               ├── Launcher.kt
        │               ├── SampleController.kt
        │               ├── SampleModel.kt
        │               ├── SampleService.kt
        │               └── SampleView.kt
        └── resources
            ├── application.properties
            ├── messages.properties
            ├── resources.properties
            └── sample
                └── javafx
                    └── kotlin
                        └── sample.fxml

There are 2 more production sources we need to take care of: the configuration (Config.kt) and the Dependency Injection setup (ApplicationModule.kt). The former informs the runtime about common settings and how the MVC members come together. The latter is used to specify the location of this configuration file as it's not no the default package as expected by the default settings. As you will see, you can override many if not all of Griffon's default settings; one of the driving goals for the framework's design is that there's no single way to do things, you may want to follow different conventions than the ones set forth by the framework, if so the framework should follow your lead instead of the other way round. We also have a group of resource files, whose main purpose is to define runtime metadata, i18n messages, and the JavaFX UI using the FXML declarative format. Remember the build has a block that configures how resources should be copied? Well these are those resources files.

application.properties

application.name=${application_name}
application.version=${application_version}
griffon.version=${griffon_version}

messages.properties

name.label = Please enter your name
greeting.default = Howdy stranger!
greeting.parameterized = Hello {0}

The Launcher script is responsible for starting the application.

Launcher.kt

package sample.javafx.kotlin

import griffon.javafx.JavaFXGriffonApplication

fun main(args: Array<String>) {
    JavaFXGriffonApplication.main(args)
}

To be honest a simple application like this one does not require a custom launcher class, it would be enough to configure JavaFXGriffonApplication as the launcher class; the reason for this launcher to exist is to let you know that you can configure the Local, System properties, or any other initial setup before the actual application is started. The next file is the application's configuration

Config.kt

package sample.javafx.kotlin

import griffon.util.AbstractMapResourceBundle

class Config : AbstractMapResourceBundle() {
    override fun initialize(entries: MutableMap<String, Any>) {
        entries.put("application", hashMapOf(
                "title"         to "JavaFX + Kotlin",
                "startupGroups" to listOf("sample"),
                "autoshutdown"  to true
        ))
        entries.put("mvcGroups", hashMapOf(
                "sample" to hashMapOf(
                        "model"      to "sample.javafx.kotlin.SampleModel",
                        "view"       to "sample.javafx.kotlin.SampleView",
                        "controller" to "sample.javafx.kotlin.SampleController"
                )
        ))
    }
}

This class defines a ResourceBundle in the form of a Map. It makes use of Kotlin's map syntax in order to define each entry. Here you can see how the MVC configuration comes together. Of important note is the value of the startupGroups key, it defines the set of MVC groups that should be initialized at bootstrap. Notice it matches the name of the group we defined, thus the application will be sure to startup this group, which means it'll create all MVC members and configure the UI accordingly. Now as mentioned earlier, this configuration file is not at its expected default location meaning we must tell the application about the new location. We do this by overriding the default settings in ApplicationModule

ApplicationModule.kt

package sample.javafx.kotlin

import griffon.core.injection.Module
import griffon.util.AnnotationUtils.named
import org.codehaus.griffon.runtime.core.injection.AbstractModule
import org.codehaus.griffon.runtime.util.ResourceBundleProvider
import org.kordamp.jipsy.ServiceProviderFor
import java.util.ResourceBundle

@ServiceProviderFor(Module::class)
class ApplicationModule : AbstractModule() {
    override fun doConfigure() {
        bind(ResourceBundle::class.java)
                .withClassifier(named("applicationResourceBundle"))
                .toProvider(ResourceBundleProvider("sample.javafx.kotlin.Config"))
                .asSingleton()
    }
}

Modules are responsible for providing type definitions (or bindings as we call them) that will be used to configure the Dependency Injection container. You can override a matching binding with your own, and that's exactly what we're doing here. Notice the usage of ::class.java as the Griffon runtime expects a Java class, not a Kotlin class. On to the MVC members we go, looking at the Model first.

Model classes in Griffon are responsible for holding properties and data that can be used between Controller and View to perform their tasks, in other words, these models follow the Presentation Model pattern. Our Model in this case is quite simple, as it holds only two properties: one for reading the the user's input and the other to store the transformation expressed as an output.

SampleModel.kt

package sample.javafx.kotlin;

import griffon.core.artifact.GriffonModel
import griffon.metadata.ArtifactProviderFor
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonModel

@ArtifactProviderFor(GriffonModel::class)
class SampleModel : AbstractGriffonModel() {
    private var _input: StringProperty = SimpleStringProperty(this, "input", "")

    var input: String
        get() = _input.get()
        set(s) = _input.set(s)

    fun inputProperty() = _input

    private var _output: StringProperty = SimpleStringProperty(this, "output", "")

    var output: String
        get() = _output.get()
        set(s) = _output.set(s)

    fun outputProperty() = _output
}

Kotlin has a concept for properties, akin to what we know as the Java Beans convention. We'd like input and output to be observable properties and leverage JavaFX's powerful binding mechanism, but we'd also want to have plain getters/setters. The code we see for each property defines a private field that holds the actual observable property, while the getter/setter exposes the plain value. Kotlin's syntax makes it a breeze to define these properties. By the way, I haven't followed on what are the recommended coding patterns for Kotlin thus I decided to use an _ at the beginning of the private field name in order to avid collision with the actual property definition. In this regard I've found Go's approach to Public/private (or Exported/unexported in their parlance) symbols to be refreshing. Now, I'm going to show the logic of the application first (Controller and Service) instead of the UI because the decisions we'll make in this layer will affect the View code.

SampleController.kt

package sample.javafx.kotlin

import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonController
import javax.annotation.Nonnull
import javax.inject.Inject

@ArtifactProviderFor(GriffonController::class)
class SampleController : AbstractGriffonController() {
    @set:[MVCMember Nonnull]
    lateinit var model: SampleModel

    @Inject
    lateinit var sampleService: SampleService

    @ControllerAction
    fun sayHello() {
        model.output = sampleService.sayHello(model.input)
    }
}

The Controller requires two collaborators in order to perform its job: the Model and a Service. Notice that the property definitions for each collaborator uses the lateinit keyword. This is a promise to the compiler that the references will be initialized to non null values, which is something we expect given that the references will be injected at runtime. Also of important note is the way Kotlin allows you to define multiple annotations on the filed, setter, and getter of a property. For the model case we define two annotations on its setter method. Controller actions are regular public methods. The implementation of the sayHello action (remember that name, it'll come again when we see the View), sends the input to the service and writes the transformation back to the model's output. Now for the service

SampleService.kt

package sample.javafx.kotlin

import griffon.core.artifact.GriffonService
import griffon.metadata.ArtifactProviderFor
import griffon.util.GriffonNameUtils.isBlank
import org.codehaus.griffon.runtime.core.artifact.AbstractGriffonService

@ArtifactProviderFor(GriffonService::class)
class SampleService : AbstractGriffonService() {
    fun sayHello(input: String?): String {
        return if (isBlank(input)) {
            application.messageSource.getMessage("greeting.default")
        } else {
            application.messageSource.getMessage("greeting.parameterized", listOf(input))
        }
    }
}

An interesting feature of Kotlin is that "if statements" as we know them in Java are actually expressions, thus the usage of the return keyword followed by the if keyword. This code looks quite trivial however there's a very, very (did I say very already?) important aspect: the action's code is executed outside of the UI thread by default as Griffon assumes you may want to run code that should be inside the UI thread such as database call, file access, a network call, or any other kind of computation. This default setting can be overridden of course. And of course we have to send the result back to the UI which means we have to follow the golden rule of Java UI programming again:

All UI related operations, such as painting, redraw, read/write widget properties, must occur inside the UI thread. All non-UI related operations, such as disk access, network calls, computations, must occur outside of the UI thread.

I can't stress enough how important this rule is. So, if the Controller updates the model outside the UI thread how do we make sure the update is executed inside the UI thread? Aha! That's the trick and the answer lies in how the View is structured

SampleView.kt

package sample.javafx.kotlin

import griffon.core.artifact.GriffonController
import griffon.core.artifact.GriffonView
import griffon.inject.MVCMember
import griffon.javafx.beans.binding.UIThreadAwareBindings
import griffon.metadata.ArtifactProviderFor
import javafx.beans.property.SimpleStringProperty
import javafx.beans.property.StringProperty
import javafx.fxml.FXML
import javafx.scene.Group
import javafx.scene.Scene
import javafx.scene.control.Label
import javafx.scene.control.TextField
import javafx.scene.paint.Color
import javafx.stage.Stage
import javafx.stage.Window
import org.codehaus.griffon.runtime.javafx.artifact.AbstractJavaFXGriffonView
import javax.annotation.Nonnull

@ArtifactProviderFor(GriffonView::class)
class SampleView : AbstractJavaFXGriffonView() {
    @set:[MVCMember Nonnull]
    lateinit var controller: SampleController
    @set:[MVCMember Nonnull]
    lateinit var model: SampleModel

    @FXML
    lateinit private var input: TextField
    @FXML
    lateinit private var output: Label

    override fun initUI() {
        val stage: Stage = application.createApplicationContainer(mapOf()) as Stage
        stage.title = application.configuration.getAsString("application.title")
        stage.width = 400.0
        stage.height = 120.0
        stage.scene = _init()
        application.getWindowManager<Window>().attach("mainWindow", stage)
    }

    private fun _init(): Scene {
        val scene: Scene = Scene(Group())
        scene.fill = Color.WHITE

        val node = loadFromFXML()
        input.textProperty().bindBidirectional(model.inputProperty())
        output.textProperty().bind(UIThreadAwareBindings.uiThreadAwareStringProperty(model.outputProperty()))
        (scene.root as Group).children.addAll(node)
        connectActions(node as Any, controller as GriffonController)
        return scene
    }
}

sample.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity"
            minHeight="-Infinity" minWidth="-Infinity"
            prefHeight="80.0" prefWidth="384.0"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="sample.javafx.kotlin.SampleView">
    <children>
        <Label layoutX="14.0" layoutY="14.0" text="Please enter your name:"/>
        <TextField fx:id="input" layoutX="172.0" layoutY="11.0"
                   prefWidth="200.0"/>
        <Button layoutX="172.0" layoutY="45.0"
                mnemonicParsing="false"
                prefWidth="200.0"
                text="Say hello!"
                fx:id="sayHelloActionTarget" />
        <Label layoutX="14.0" layoutY="80.0" prefWidth="360.0" fx:id="output"/>
    </children>
</AnchorPane>

We see the usage of Kotlin's annotation syntax once again, as well as lateinit; not only do we use it once more for dependencies that are injected via the DI container but also those injected by FXMLLoader (check out this FXMLLoader article available at Oracle Java Magazine). Right about the bottom of the code we discover the answer to asserting the UI is updated at the right time and obeying the golden rule of Java UI programming

output.textProperty().bind(UIThreadAwareBindings.uiThreadAwareStringProperty(model.outputProperty()))

That's right, as explained on a previous post Griffon delivers a wide range of utilities that make writing JavaFX application a much easier task. The advantage of following pattern allow us to define data on Models without caring too much about the actual Thread on which the data will be consumed; the same applies to the Controller and Services. We can test all these components completely separate from the View and the UI. The last file we look at is the declarative UI definition with FXML, which contains two important things:

  • the definition of an fx:controller pointing to the View class as this is the class where the @FXML annotations are used.
  • the name of the button matches the name of the controller action plus a prefix.

And that's all for now. In you inspect the code available at GitHub you'll notice there are a few more sources such as unit, integration, and functional tests, as well as regular Groovy Gradle build file. The reason being is that I did not find enough documentation to properly adapt the Groovy version of the build to the Kotlin one, thus the Groovy version performs a few more tasks than its Kotlin counterpart, for now. As a final token, these are the code stats for the project

+---------------------------------+-------+-------+
| Name                            | Files |   LOC |
+---------------------------------+-------+-------+
| FXML Sources                    |     1 |    22 |
| Kotlin Functional Test Sources  |     1 |    32 |
| Kotlin Integration Test Sources |     1 |    31 |
| Kotlin Sources                  |     7 |   139 |
| Kotlin Test Sources             |     1 |    46 |
| Properties                      |     3 |     6 |
+---------------------------------+-------+-------+
| Totals                          |    14 |   276 |
+---------------------------------+-------+-------+

There you have it folks, building a JavaFX application with Griffon and Kotlin is as straightforward as writing it with Java or Groovy. Remember you can use the Lazybones project templates as explained at the Getting Started tutorial if you just want to get off the ground quickly, or write all files by hand, or mix both approaches, your choice.

Happy coding.

Liked it? Take a second to support aalmiray on Patreon!
Become a patron at Patreon!

4 comments

  • Hi! When I try run app by gradle (gradle run) i take – error: cannot find symbol
    @FXML
    ^
    symbol: class FXML
    location: class SampleView

    Could you help me?

    • That’s odd, @FXML is an annotation provided by the JavaFX runtime (jfxrt.jar) and as such should be available on the compile classpath. Are you trying out the code with Java8?

      • Thank you, for quick answer! By default open jdk on Ubuntu dont contains java FX. 🙁
        so
        sudo apt-get install openjfx
        resolved our problem.

        Griffon framework look very nice, cheers

        • I’ve added an update at the top to let other readers not about this particular setting. Thank you! 🙂

Trackbacks/Pingbacks

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

ˆ Back To Top