In the wonderful world of Kotlin, libraries and frameworks are often released that are adapted on top of existing java frameworks. Many of these projects remove a lot of unnecessary “boilerplate” that is often present in particular java frameworks and allows the developer experience to be much more efficient.

In this blog, we will be looking at a JavaFX Framework for Kotlin, TornadoFX.

For those who aren’t familiar with JavaFx, it is a platform for creating desktop applications. It works well for on-premise business applications and is capable of handling large amounts of data easily which makes “it a practical choice for applications used behind the corporate firewall” 1.

In this blog post, I will be going through a very basic application that demonstrates the usefulness of TornadoFX and event-driven UIs using a traffic light system.

Traffic Light FX

TornadoFX

Setting up a TornadoFX application is pretty straight forward. And, although I will not be going through that in this blog, by following the guide created by Edvin Syse, it can be done easily.

In my application, I have arranged the structure of the application into a typical MVC pattern as seen below.

MVC Folder Structure

User Interface

First, we need to get an overview of the application.

Let’s look at the main view: TrafficLightContainer. Here, we set the container to inherit Tornado’s View class and then incorporate the Kotlin DSL to create and style elements inside the container. The vbox is a vertical box container that positions new elements below previous elements.

Below I have demonstrated the in-line styling that can be used such as spacing or alignment and also the external styling by adding a class on an element. This behaves exactly like most HTML based frameworks by allowing the inline style to override the external class.

class TrafficLightContainer : View() {

    override val root = vbox {
        spacing = 25.0
        alignment = Pos.CENTER
        addClass(Styles.trafficLightContainer)
        add<TrafficLight>()
        add<RedEventButton>()
        add<YellowEventButton>()
        add<GreenEventButton>()
    }
}

Here I am creating a trafficLightContainer css class and setting its and the generic label’s class styling:

private const val CONTAINER_WIDTH = 250.0
private const val CONTAINER_HEIGHT = 500.0
private const val LABEL_DIMENSION = 100.0

class Styles : Stylesheet() {

    companion object {
        val trafficLightContainer by cssclass()
    }

    init {
        trafficLightContainer {
            minHeight = CONTAINER_HEIGHT.px
            maxHeight = CONTAINER_HEIGHT.px
            minWidth = CONTAINER_WIDTH.px
            maxWidth = CONTAINER_WIDTH.px
            backgroundColor += Paint.valueOf("black")
        }
        label {
            minHeight = LABEL_DIMENSION.px
            maxHeight = LABEL_DIMENSION.px
            minWidth = LABEL_DIMENSION.px
            maxWidth = LABEL_DIMENSION.px
            alignment = Pos.CENTER
        }
    }
}

Event-Driven UI

So, how does an event-driven UI work in TornadoFX?

Using the Event Bus we can decouple interactions between views by firing events on one view and then listening for said events on the other view - similar to how Angular’s EventEmitter works.

Here we have created a TrafficLightEvent that inherits from the FXEvent class:

class TrafficLightEvent(val colour: TrafficLightColour) : FXEvent()

The event’s constructor is passed a TrafficLightColour enum, which represents the current state of the traffic lights:

enum class TrafficLightColour { RED, YELLOW, GREEN }

In the TrafficLight class, we listen for TrafficLightEvents, fired when the user presses one of the buttons. Then we call the TrafficLightController to get the updated information used to set the text and background properties of the view by setting the state equal to the colour from the TrafficLightEvent event.

const val BORDER_RADIUS = 100.0

class TrafficLight : View() {
    private val trafficLightController: TrafficLightController by inject()
    private val eventRegistration: FXEventRegistration
    private val labelBackground = SimpleObjectProperty<Background>()
    private val labelText = SimpleStringProperty()
    private var state: TrafficLightColour = RED

    override val root = label {
        backgroundProperty().bind(labelBackground)
        textProperty().bind(labelText)
    }

    init {
        eventRegistration = subscribeToButtonEvents()
        setUiFromState(state)
    }

    override fun onDelete() {
        eventRegistration.unsubscribe()
        super.onDelete()
    }

    private fun subscribeToButtonEvents() = subscribe<TrafficLightEvent> {
        trafficLightEvent -> if (trafficLightEvent.colour != state) setUiFromState(trafficLightEvent.colour)
    }

    private fun setUiFromState(trafficLightColour: TrafficLightColour) {
        state = trafficLightColour
        val newBackground = getBackground(trafficLightController.getColourCodeForState(state))
        val newText = trafficLightController.getTextForState(state)
        labelBackground.set(newBackground)
        labelText.set(newText)
    }

    private fun getBackground(colourCode: String) =
        Background(BackgroundFill(c(colourCode), CornerRadii(BORDER_RADIUS), insets()))
}
const val RED_COLOUR_CODE = "#FF4C4C"
const val YELLOW_COLOUR_CODE = "#FFFF00"
const val GREEN_COLOUR_CODE = "#019A01"

class TrafficLightController : Controller() {

    fun getTextForState(trafficLightColour: TrafficLightColour) = when(trafficLightColour){
        RED -> RED_BUTTON_TEXT
        YELLOW -> YELLOW_BUTTON_TEXT
        GREEN -> GREEN_BUTTON_TEXT
    }

    fun getColourCodeForState(trafficLightColour: TrafficLightColour) = when(trafficLightColour){
        RED -> RED_COLOUR_CODE
        YELLOW -> YELLOW_COLOUR_CODE
        GREEN -> GREEN_COLOUR_CODE
    }
}

So, while the TrafficLight view listens for the events, we need something to fire the events, this is where the EventButton comes in:

abstract class EventButton: View() {
    private val buttonText = SimpleStringProperty()

    override val root = button {
        bind(buttonText)
        setOnAction {
            fireTrafficLightEvent()
        }
    }

    open fun fireTrafficLightEvent() {}

    fun setButtonText(text: String) = buttonText.set(text)
}

The coloured event buttons just inherit this class and fire their respective colours to the UI when clicked:

const val GREEN_BUTTON_TEXT = "GO"

class GreenEventButton: EventButton() {
    override fun fireTrafficLightEvent() = fire(TrafficLightEvent(TrafficLightColour.GREEN))

    init { setButtonText(GREEN_BUTTON_TEXT) }
}

Conclusion

And, that’s it! As you can see this is a very basic application, but it does demonstrate the usefulness of using an event-driven UI. Especially when it’s able to be written using a DSL and various other niceties given to us by Kotlin, which make the developer experience much more satisfying.

Notes:

If you’re interested in the Traffic Light project, you can access it here.