The State of JVM Desktop Frameworks: TornadoFX
A professed Java geek takes a look at JavaFX and Tornado FX, two options developers can use to build upon existing APIs in Java and Kotlin.
Join the DZone community and get the full member experience.Join For Free
The two previous posts of this series were respectively dedicated to Swing and SWT. This post is dedicated to Tornado FX, which itself is built on JavaFX.
- The State of JVM Desktop Frameworks: Introduction
- The State of JVM Desktop Frameworks: Swing
- The State of JVM Desktop Frameworks: SWT
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 has just received 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 and can also be installed separately from the JDK."
Compared to Swing, JavaFX adds an application abstraction. Here's an overview of the JavaFX API:
Besides, you can create a JavaFX user-interface by taking two different approaches:
- Define all objects in pure Java code.
- Use XML-based layout files (FXML) that integrate with Java code.
Kotlin allows for improving upon a Java API to provide a better developer experience. We could do that by ourselves. But the Tornado FX project already takes care of it.
Here's an a bird's-eye view of the API:
Tornado FX has a couple of benefits compared to plain JavaFX. Here are some of them.
Components and Layouts DSL
Like Groovy, Kotlin allows us to create usable DSLs. Unlike Groovy, the DSLs created are type-safe by default. You can find two of my previous experiment with DSLs here:
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:
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:
While it might seem not that readable, the IDE can be of tremendous help. With IntelliJ IDEA, you can fold the un-important bits:
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.
Tornado FX's controllers implement the C part in the MVC pattern. They are responsible for encapsulating 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 what to do 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:
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 prove possible to reuse UI components with different logic. The existing design allows for the reuse of the same logic within different UI components, which is much less frequent.
Tornado FX provides Dependency Injection. The API provides two ways to inject dependencies:
Explicitly call the
inject() is available in
Controller but you need to use
find() in other classes.
TornadoFX provides a singleton-scoped Event Bus. Its usage is nothing but classical.
Event classes must inherit from an
FXEvent superclass. TornadoFX requires that we set the thread that manages the event, whether it's applicative or runs in the 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:
- Called when the Event Bus receives a
- Send a
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 updates anyway. Whether you like it or not, JavaFX is part of the available options.
The complete source code for this post can be found on GitHub in Maven format.
To go further:
Originally published at A Java Geek on January 31, 2021.
Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.