The state of JVM desktop frameworks: TornadoFX
Published: 2021-01-31 17:02:25

This post is dedicated to Tornado FX, which itself is built on JavaFX.

## JavaFX

JavaFX started as a scripting language named [JavaFX script](https://en.wikipedia.org/wiki/JavaFX_Script). Sun Microsystems intended to use it to compete with Adobe Flex (now [Apache Flex](https://en.wikipedia.org/wiki/Apache_Flex)) and [Microsoft Silverlight](https://www.microsoft.com/silverlight/) to a lesser extent.

In 2010, at Java One, Oracle, which had bought Sun in the meantime, announced that it would stop the development of the language while keeping the API. With Java 8 - released in 2014, JavaFX became the official successor of the Swing API: the latter just got bug fixes since then.

>In the past JavaFX was included in the Oracle JDK up till version 11.
>
>But it has always been [a separate project](https://openjfx.io) and can also be installed separately from the JDK.
>
>-- From [Starting a JavaFX Project with Gluon Tools](https://foojay.io/today/starting-a-javafx-project-with-gluon-tools/)

Compared to Swing, JavaFX adds an _application_ abstraction. Here's an overview of the JavaFX API:

![Overview of the JavaFX API](https://dev-to-uploads.s3.amazonaws.com/i/mwlywzzinuzwkvq1g41h.png)

Besides, you can create a JavaFX user-interface by taking two different approaches:

1. Either define all objects in pure Java code
2. Or use XML-based layout files (FXML) that integrate with Java code

Here's a sample of [the former](https://github.com/FDelporte/JavaOnRaspberryPi/tree/master/Chapter_07_JavaFX/javafx-minimal) and [the latter](https://github.com/FDelporte/JavaOnRaspberryPi/tree/master/Chapter_07_JavaFX/javafx-minimal-fxml) for the same application.

## Tornado FX

Kotlin allows improving a Java API to provide a better developers-experience. We could do that by ourselves. But the [Tornado FX](https://tornadofx.io/) project already takes care of it.

Here's an a birds eye view of the API:

![Overview of the Tornado FX API](https://dev-to-uploads.s3.amazonaws.com/i/xktaue3uo8h4fzblbeo1.png)

Tornado FX has a couple of benefits compared to plain JavaFX. Here are some of them.

### Components and layouts DSL

Like Groovy, Kotlin allows to create usable <abbr title="Domain-Specific Language">DSL</abbr>s. Unlike Groovy, the DSLs created are type-safe by default. You can find two of my previous experiment with DSLs;

* [Kaadin](https://github.com/nfrankel/kaadin), a DSL to create user interfaces with Vaadin
* [An embryo of a DSL](https://github.com/nfrankel/hazelcast-kotlindsl) to configure Hazelcast

Likewise, Tornado FX provides all out-of-the-box components and layouts of JavaFX via a DSL. Here's an example of how it looks like:

```kotlin
vbox {
  text("Name")
  textfield()
  button("Button").setOnAction {
  println("Button pressed")
  }
}
```

Some layouts allow for more complex configuration. For example, JavaFX offers a `GridPane` layout, similar to AWT's `GridbagLayout`. You need to pass the configuration as a `GridPaneContraints` object for each laid out element. Here's a sample:

```kotlin
override val root = gridpane {
  padding = insets(space)
  textfield {
    gridpaneConstraints {
      columnIndex = 0
      fillWidth = true
      hGrow = Priority.ALWAYS
      marginBottom = space
    }
  }
  button("Button") {
    gridpaneConstraints {
      columnIndex = 1
      hAlignment = HPos.RIGHT
      marginBottom = space
    }
  }
  textfield {
    gridpaneConstraints {
      rowIndex = 1
      fillWidth = true
      hGrow = Priority.ALWAYS
    }
  }
  textfield {
    gridpaneConstraints {
      columnRowIndex(1, 1)
      fillWidth = true
      hGrow = Priority.ALWAYS
      marginLeft = space
    }
  }
}
```

While it might seem not that readable, the IDE can be of tremendous help. With IntelliJ IDEA, you can fold the un-important bits:

![Screenshot of IntelliJ IDEA folding feature](https://dev-to-uploads.s3.amazonaws.com/i/c6u5p1byqyohhp8qjsgw.jpg)

I tend to prefer to create dedicated classes for each component when possible instead of using a generic class that is configured when instantiated. It doesn't work well with an existing DSL, as I need to supplement it with my own.

### Controllers

Tornado FX's controllers implement the **C** part in the [MVC pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller). They are responsible to encapsulate business logic. The UI thread should never run long-running tasks because it will make it unresponsive. Since controllers may execute such tasks, you should decide on a case-by-case basis. Finally, you can inject a controller (link:#dependency-injection[see below]) into other components as singletons.

Not that the API doesn't enforce any requirement. It's up to the developer to design controllers according to the above guidelines.

For example, here's a controller that fires an event when it received one of another kind:

```kotlin
class PathModelController : Controller() {
  init {
    subscribe<DirectoryPathUpdatedEvent> {
      fire(PathModelUpdatedEvent(it.path))
    }
  }
}
```

Tornado FX's controllers are injectable into views. That approach couples the view to the logic. I'd rather have it the other way around: inject views into controllers. Thus, it would possible to reuse UI components with different logic. The existing design allows reusing the same logic within different UI components, which is much less frequent.

### Dependency Injection

Tornado FX provides Dependency Injection. The API provides two ways to inject dependencies:

1. Use the `inject()` delegate:

    ```kotlin
    class MyView: View("My View") {
      val myController: MyController by inject()
      val myController2 by inject<MyController>()
    }
    ```

2. Explicitly call the `find()` function:

    ```kotlin
    class MyView: View("My View") {
      val myController = find(MyController::class)
      val myController2 = find<MyController>()
    }
    ```

Note that `inject()` is available in `View` and `Controller` but you need to use `find()` in other classes.

### Event Bus

TornadoFX provides a singleton-scoped Event Bus. Its usage is nothing but classical.

Event classes must inherit from an `FXEvent` superclass. TornadoFX requires to set the thread that manages the event, whether applicative or background. Long-running tasks should run on background threads.

`Component` offers a `fire()` function that will push the event to the bus. It also offers the `register()` function that will notify about the reception of an event, filtered by type.

Here's how it looks like:

```kotlin
class FooEvent: FXEvent(BackgroundThread)
class BarEvent: FXEvent(BackgroundThread)

class Dummy {
  init {
    subscribe<FooEvent> {
      println(it)           // 1
    }
  }
  fun bar() {
    fire(BarEvent())        // 2
  }
}
```

1. Called when the Event Bus receives a `FooEvent`
2. Send a `BarEvent`

## Conclusion

After having developed just a simple demo application, I can form an opinion neither on JavaFX nor on Tornado FX. More experience is necessary. I like the embedded Event Bus but I dislike the design of the relationships between controllers and UI components.

In all cases, as mentioned in the preamble, Swing won't get any update anyway. Whether you like it or not, JavaFX is part of the available options.

Thanks to [Hendrik Ebbers](https://twitter.com/hendrikebbers) and [Frank Delporte](https://twitter.com/frankdelporte) for their review of this post.

The complete source code for this post can be found on GitHub:

{% github ajavageek/renamer-tornado %}

**To go further:**

* [JavaFX](https://openjfx.io/)
* [JavaFX vs. Swing](http://tutorials.jenkov.com/javafx/index.html#javafx-vs-swing)
* [Exhaustive JavaFX tutorial](http://tutorials.jenkov.com/javafx/)
* [Tornado FX](https://tornadofx.io/)

_Originally published at [A Java Geek](https://blog.frankel.ch/state-jvm-desktop-frameworks/4/) on January 31<sup>th</sup> 2021_