Introduction to TornadoFX Part 2

Amanda Hinchman
Kotlin Thursdays
Published in
5 min readJun 1, 2018

--

Last week, we started our neighborhood cat scheduler application. I may switch this series to occur on Thursdays, given the craziness in my personal life! Don’t forget you can view the full version of this project on my github!

This week, we wrap up our mini-application with a couple more cool TornadoFX features that make native development really fun!

Topics Covered

  • Kotlin: basic syntax, idioms, Kotlin convention. Let me know if I didn’t explain anything and I’ll be sure to answer and update!
  • TornadoFX: scoping, data-binding, fragments, and element overlays.

Documentation Reference

Taking a Pause for a Little Refactoring

Keep it DRY, right? Good coding conventions indicate that if you don’t have to repeat yourself, don’t. One thing I noticed right away is that my setup for tabbed tableviews is incredibly repetitive.

Refactoring is a skill that improves over time, so always ask your coding buddies to review your code! Like in Java, you can use forEach in Kotlin:

That’s a lot cleaner. Let’s continue on with some of the cool features we might want in our application.

Editing your Schedule

Occasionally, life gets in the way of life; you definitely want the option to be able to change your designated times you might want to visit a client’s house or even correct the names you originally assigned for owners/cats.

One of the fresh features TornadoFX offers is the way you can leverage your data with tableviews — advanced data controls such as smartResize() and remainingWidth(), designed to give pleasant spacing for the data necessary in your columns. More importantly, databinding models/listening for changes for these tables have never been easier with TornadoFX. In the last post, we used Kotlin data classes to display our data; we will now edit our model with ViewModel, TornadoFX’s tool that helps cleanly separate your UI and business logic that allows for features like dirty-state checking and commits/rollback. With ViewModel, you can avoid manual rebinding of your data as it changes and tight-coupling (having to extract the object data again just to be able to reflect changes). You no longer have to bind, unbind, or rebind on change for a simple action such as editing your values. Whew!

Another perk of ViewModel is not having to worry about changing your data as you insert that data in ObservableValue fields until you call model.commit(). Once commit has been called, the data in the facade is flushed back into our person object and the table will now reflect our changes. Below, we use an extension of ViewModel called ItemViewModel that simply allows for easy getter/setter access via the item property.

class CatSchedule(ownerName: String, catName: String, address: String,
time: String, catImage: String) {
val ownerNameProperty = SimpleStringProperty(this, "", ownerName)
var ownerName by ownerNameProperty

val catNameProperty = SimpleStringProperty(this, "", catName)
var catName by catNameProperty

val addressProperty = SimpleStringProperty(this, "", address)
var address by addressProperty

val timeProperty = SimpleStringProperty(this, "", time)
var time by timeProperty

val catImageProperty = SimpleStringProperty(this, "", catImage)
var catImage by catImageProperty
}

class CatScheduleModel: ItemViewModel<CatSchedule>() {
val ownerName = bind(CatSchedule::ownerNameProperty)
val catName = bind(CatSchedule::catNameProperty)
val address = bind(CatSchedule::addressProperty)
val time = bind(CatSchedule::timeProperty)
val catImage = bind(CatSchedule::catImageProperty)
}

All this data-binding can be leveraged with TornadoFX: let’s add a couple lines to our refactored tableview code. You may notice a new identifier I’m using in one of the global variables:

  • lateinit : this is a Kotlin concept that adheres to Kotlin’s null-safety feature. Non-null types must always be supplied with a constructor, if you can’t, but you still want to avoid null checks when referencing the property inside the body of a class.

Using this model, not only can we display our data, but we can call onUserSelect(# of clicks) to:

  • (1): select a cat according to their avi
  • (2): open the editor, passing current data from scopes
Look at all these cute cats!

Very nice! Let’s talk about how we’re able to pass the table data to a new window.

Scopes

According to TornadoFX, you get singleton instances when you use inject() or find() to locate a Controller or a View — meaning that wherever you locate that object in your code, you will get back the same instance. Scopes provide a way to make a View or Controller unique to a smaller subset of instances in your application. Scopes are simple constructs that can be used generally and once you understand its basic application, can be used for any limit you can stretch your mind to!

You define your own scope class to use the model property only available to this scope so that you may keep states.

class CatScheduleScope:  Scope() {
val model = CatScheduleModel()
}

With this scope instance, you may pass your selected data to the editor class.

You’ll notice in the editor class already has an instance of the CatScheduleModel, so we configured the super.scope of the component Fragment when we open the editor. The editor class now holds information passed from the selection in the tableview, and can be displayed with ObservableValues in our textfield nodes. Last but not least, only allow the option to save when the model gets dirty and commit those changes!

You’ll notice the use of Fragment for this particular component — any View you create is a singleton, meaning only one can exist at a time in the parent view. Fragments, on the other hand, is used for multiple instances, which is especially useful for nested popups or working parts of a larger UI.

Cat Bubbles

As much as I wanted to add animation to this tutorial, I’m afraid that will go well beyond the scope of TornadoFX and the introductory aspects of JavaFX — but for practicality’s sake, let’s quickly gloss over how stackpane can be used to add anything you wish as an overlay to your views.

I edited these images as a single bubble icon, but what I describe is extremely similar to a concept discovered in a previous TornadoFX project for drag-and-drop GUI creation (you can also look into this project for my own version of scope use!). The concept is really simple! Create a stackpane as the main portion of the view, set isMouseTransparent = true so that you can click through the upper overlay, and set the opacity of the stackpane with any color with the opacity closest to 0 as possible.

This isn’t TornadoFX related, but this is a concept I find incredibly useful in creating native applications.

Next week

I may have to switch to a different name for Kotlin Tuesdays: my life has been really hectic lately (and I just accepted another job offer, so I may well be moving again!). Please bear with me while I try to make get my life a little more stable!

Next week, we explore Kotlin with a Spring Framework. I personally haven’t tried web development with Kotlin, but this is one I’m REALLY excited for. Stay tuned for next week!

--

--

Amanda Hinchman
Kotlin Thursdays

Kotlin GDE and Android engineer. Co-author of O'Reilly's "Programming Android with Kotlin: Achieving Structured Concurrency with Coroutines"