Coroutines and JavaFX GUI

I’m about to finish a simple program with JavaFX as GUI and there’s only one more thing left to do: getting the graphical user interface to update some labels with the correspondent data fetch from a suspendable function. I have built the GUI with Scenebuilder and I load the FXML file into the Main Class (the one which inherits from Application(). I would like to do this using coroutines, but as you might have guessed, I don’t have enough knowledge yet. I’ve seen quite a few tutorials and read some others as well, but there aren’t many coroutines + JavaFx tutorials out there. I will post the code of a demo app so that is simpler to read:

Basically, this is what I’m doing (Now. But I’ve tried other approaches):

HelloApplication.kt

package com.alberto.demo2

import javafx.application.Application
import javafx.fxml.FXMLLoader
import javafx.scene.Scene
import javafx.stage.Stage
import kotlinx.coroutines.*
import kotlinx.coroutines.javafx.JavaFx
import kotlin.coroutines.CoroutineContext

var controlador:HelloController = HelloController()
var contador:Int = 0

class HelloApplication : Application() {

override fun start(stage: Stage) {

    val fxmlLoader = FXMLLoader(HelloApplication::class.java.getResource("hello-view.fxml"))
    val scene = Scene(fxmlLoader.load(), 320.0, 240.0)
    stage.title = "Hello!"
    stage.scene = scene
    stage.show()

    GlobalScope.launch(Dispatchers.Default){

        while(true) {
            updateLabel()
            delay(1000)
        }
    }
}

}

fun updateLabel(){

contador++
controlador.welcomeText.text = "$contador"

}

fun main() {

Application.launch(HelloApplication::class.java)

}

HelloController.kt

package com.alberto.demo2

import javafx.fxml.FXML
import javafx.scene.control.Label
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

class HelloController {
@FXML
var welcomeText: Label = Label()

@FXML
private fun onHelloButtonClick() {


            welcomeText.text = "Welcome to JavaFX Application!"

}

}

At this moment, I’m getting this output:

Exception in thread “main” java.lang.ExceptionInInitializerError
at com.alberto.demo2.HelloController.(HelloController.kt:11)
at com.alberto.demo2.HelloApplicationKt.(HelloApplication.kt:11)
Caused by: java.lang.IllegalStateException: Toolkit not initialized
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:410)
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:405)
at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:695)
at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:657)
at javafx.scene.control.Control.(Control.java:99)
… 2 more
I’ve tried other approach that didn’t throw any exceptions but neither did update the label with the increment of “contador” (counter).

If you have any suggestions for me to try or know about any good article that can make things clearer than other articles out there, about coroutines, please let me know.

Thank you very much in advance!

Alberto.

I won’t help you too much with JavaFX stuff, but I believe you need to put code that touches the UI (welcomeText.text = line) inside withContext (Dispatchers.Main) { ... }, so it will be executed within the main thread.

1 Like

The whole trick of using coroutines with JavaFX is to add kotlinx.coroutines/ui/kotlinx-coroutines-javafx at master · Kotlin/kotlinx.coroutines · GitHub to your dependencies and use withContext(Dispatcher.JavaFx to wrap any UI changes.

You should not use Main for that.

2 Likes

Hmm, why is that? Isn’t it basically the same as Dispatcher.JavaFx?

For example, if you are writing JavaFx application, you can use either Dispatchers.Main or Dispachers.JavaFx extension, it will be the same object.

JavaFX, unlike Swing, has its own dispatch thread and scheduler. If UI changes are dispatched on other threads, it usually throws an error.

I am not sure if JavaFx generates Main alias, but if you try to use Swing main thread, it will be an error.

I believe it does. My understanding is that there is no “just Main dispatcher”. Dispatchers.Main is not available outside of UI frameworks. It is is provided by either Swing, JavaFx or Android, depending on which we use.

Imao, Kotlin has little to do with this question.

JavaFX updates UI in its JavaFX Application Thread. If we need to change the text of a Label in other threads (like Main in this situation), we should use code below:

Platform.runLater { 
    label.text = "some text" 
}

The code in the block will run in the JavaFX Application Thread.

And what if we like to run UI code synchronously? One of the main points of coroutines is to not use these ugly callbacks anymore, but instead write our code as normal, sequentially. withDispatcher(Dispatchers.Main) {} does this, Platform.runLater {} is a step back. Also, it is not multiplatform, withDispatcher() is.

In my own project, javafx.concurrent.Task<V> is used to run long-time task. Run UI code synchronously may cause problems, like swing did before.

What kind of problems? Blocking UI? Not when using coroutines.

For a example, if we have a ComboBox and there are some items in it. In another thread we get the items of the ComboBox and select the last one. But at the mean time, in the UI thread we clear the ComboxBox. Then, back to the select thread, we will select a non-existing item.

We may want to get, select and then clear, but if we select in another thread, the action execution order is uncertained.

Dispatchers.JavaFX uses runLater under the hood.

I never suggested to use other threads than the UI thread. From the very beginning I suggest using Dispatchers.Main (or Dispatchers.JavaFX - I believe it’s the same) which uses “JavaFX Application Thread”.

withContext() is a native way of jumping between threads for a coroutine application. It fits there much smoother than runLater{} which is Java API and doesn’t know anything about coroutines.

Ah, I misunderstood your words, sorry.