From Swing to Compose Desktop #7: Concurrency
Tags: kotlin, java, ux
Published: 2020-12-13 12:07:22

Welcome to the 7. and final post about my journey of transforming a Java Swing app to Compose for Desktop. Today I will focus on concurrency. To get an idea why this is necessary, please take a look at this clip:

{% youtube QG0DMbU1HZY %}

Now, this looks bad. And of course it's all my fault. Look:

```kotlin
import androidx.compose.desktop.Window
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

fun main() = Window {
    Box(contentAlignment = Alignment.Center,
    modifier = Modifier.fillMaxSize()) {
        Button(onClick = {
            while (true) {
                println("ohoho...")
            }
        }) {
            Text("Hallo")
        }
    }
}
```

Once the button has been clicked, the app is stuck in an infinite loop doing nothing but `println()`. What we see here is common to many ui frameworks. They are said to be **single-threaded**, meaning that all work related to the user interface **must** be done on this particular thread. Swing calls it the **Event Dispatch(ing) Thread**, on Android it's the **main thread**. They all have it, and they all need it because creating a ui framework that can safely handle its state no matter on which thread this happens [is said to be way too complex and prone to deadlocks](https://stackoverflow.com/questions/5544447/why-are-most-ui-frameworks-single-threaded). 

This gives us two simple rules:

1. Modify ui state on *that* thread
2. Do all other work **somewhere else**

And that's why my example fails so miserably. It's not the fault of Swing, and it's not the fault of Compose for Desktop. We developers need to make sure that any heavy computation is done on a separate thread. But also the seemingly easy stuff, like file or network i/o. Yes that's right. *Anything* that is not ui related and might block other work for more than a few milliseconds should be done *somewhere else*. 

Now, being Kotlin fans we have coroutines at our disposal. They give us lightweight threads.

```kotlin
package com.thomaskuenneth

import androidx.compose.desktop.Window
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

fun main() = Window {
    Box(contentAlignment = Alignment.Center,
            modifier = Modifier.fillMaxSize()) {
        Button(onClick = {
            GlobalScope.launch {
                while (true) {
                    println("ohoho...")
                }
            }
        }) {
            Text("Hallo")
        }
    }
}
```

To use them we just need to add a dependency like this:

```
dependencies {
  implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2")
}
```

This post is no coroutine tutorial, so I am not going to explain what happens here, besides emphasizing that `GlobalScope.launch` usually is not the best practice. A coroutine scope defines a lifecycle, a lifetime, for coroutines. Most of the time `GlobalScope` is too broad. On the other hand, this way coroutines can be created most easily. So, I could add it to TKDupeFinder like this:

```kotlin
Button(
    onClick = {
      if (scanning.value) {
        stopScan(currentPos, checksums, scanning)
      } else {
        scanning.value = true
        selected.clear()
        checksums.value = emptyList()
        startScan(name.value.text, currentPos, checksums, scanning)
      }
    },
```

The idea is to start scanning for duplicate files if a button is pressed, and to stop if that button is pressed again.

```kotlin
private fun startScan(baseDir: String, currentPos: MutableState<Int>,
            checksums: MutableState<List<String>>,
            scanning: MutableState<Boolean>) {
  GlobalScope.launch(Dispatchers.IO) {
    df.clear()
    df.scanDir(baseDir, true)
    df.removeSingles()
    stopScan(currentPos, checksums, scanning)
  }
}

private fun stopScan(currentPos: MutableState<Int>,
           checksums: MutableState<List<String>>,
           scanning: MutableState<Boolean>) {
  currentPos.value = 0
  checksums.value = df.checksums.toList()
  scanning.value = false
}
```

`stopScan()` updates some state, which triggers a recomposition of the user interface. `startScan()` does the actual search. Have you noticed that I pass `Dispatchers.IO`? This specifies *where* a coroutine should be executed. There are several pools. `Dispatchers.IO` is great for i/o related work. 

Once the search is complete, I invoke `stopScan()`. Recall that we are still on `Dispatchers.IO`. This begs an interesting question. Shouldn't I make sure (using `Dispatchers.Main`) that this is done on the appropriate thread? It seems that this is not necessary. In the end, my code is not actually changing the user interface, but only modifying state. 

Using this code, TKDupeFinder works well. ...well, sort of... If I press the button to stop the scan, the ui is updated accordingly. But under the hood the scan continues. This should not surprise us, as in `stopScan()` there is no code to cancel the coroutine. `launch()` returns a `Job` which has `cancel()`. Great, right? But why the heck do I not use it, then? Coroutines need to cooperate. They must check regularly if they should continue running using something like `while (isActive) {`.

The search is done by my old Java code. As I wanted to update only the user interface and leave the scanner unchanged, invoking `isActive` is no option. So for this particular purpose, I decided to wrap the search into a classic `java.lang.Thread`.

```kotlin
private var worker: Thread? = null
private fun startScan(baseDir: String, currentPos: MutableState<Int>,
                      checksums: MutableState<List<String>>,
                      scanning: MutableState<Boolean>) {
    worker = thread {
            df.clear()
            df.scanDir(baseDir, true)
            df.removeSingles()
            invokeLater {
              stopScan(currentPos, checksums, scanning)
            }
    }
}

private fun stopScan(currentPos: MutableState<Int>,
                     checksums: MutableState<List<String>>,
                     scanning: MutableState<Boolean>) {
    if (worker?.isAlive == true) {
        worker?.stop()
    }
    currentPos.value = 0
    checksums.value = df.checksums.toList()
    scanning.value = false
}
```

This solution is certainly not ideal, as `stop()` has been deprecated for (almost) ever. Also, with `thread` I seem to have to invoke `stopScan()` on the main thread (using `invokeLater()`). But then, it just works. 😂

I hope you enjoyed this little series. I would love to hear your thoughts in the comments. I will continue working on the app. You can find it [on GitHub](https://github.com/tkuenneth/TKDupeFinder).

---
[From Swing to Jetpack Compose Desktop #1](https://dev.to/tkuenneth/from-swing-to-jetpack-compose-desktop-1-12kf)
[From Swing to Jetpack Compose Desktop #2](https://dev.to/tkuenneth/from-swing-to-jetpack-compose-desktop-2-4a4h)
[From Swing to Compose Desktop #3](https://dev.to/tkuenneth/from-swing-to-compose-desktop-3-23ck)
[From Swing to Compose Desktop #4](https://dev.to/tkuenneth/from-swing-to-compose-desktop-4-35c1)
[From Swing to Compose Desktop #5](https://dev.to/tkuenneth/from-swing-to-compose-desktop-5-4ehc)
[From Swing to Compose Desktop #6](https://dev.to/tkuenneth/from-swing-to-compose-desktop-6-4280)