From Swing to Jetpack Compose Desktop #1
Tags: kotlin, java
Published: 2020-11-24 16:18:07

Recently, JetBrains [announced](https://blog.jetbrains.com/cross-post/jetpack-compose-for-desktop-milestone-1-released/) a preview version of **Jetpack Compose for Desktop**. As Jetpack Compose is the next big thing in Android ui and I have always had a passion for the desktop I felt I just *must* know how Compose for Desktop looks and feels. The best way to learn something new is to use it and to play with it. Hence, I decided to try to transform a Java Swing user interface to Compose Desktop. 

Follow me on my journey.

The app I will be transforming is old, very old. It is called **TKDupeFinder**. Its sole purpose is to find duplicate files. Once the user has entered a base directory, the app browses through all child folders and searches for files which have the same MD5 hash. They are displayed in a list. The user can then open these files and delete the duplicates. For more than ten years the app was burried in the depths of my hard drive. A couple of days ago I created a [repo on GitHub](https://github.com/tkuenneth/TKDupeFinder) and pushed the source. This is where the transformation will take place.

The first step is to make the source code Kotlin-ready. Originally the app was written in Java using NetBeans and utilizing Maven. Although there still is a Kotlin plugin for NetBeans this seems to be no longer developed actively. So I decided to move the project to IntelliJ. I also switched to Gradle. While you can easily use Kotlin with Maven, the current Jetpack Compose for Desktop early preview documentation focusses on Gradle. Let's write a Kotlin `main()`-function that launches the Swing application:

```kotlin
package com.thomaskuenneth.tkdupefinder

import javax.swing.SwingUtilities
import javax.swing.UIManager
import javax.swing.UnsupportedLookAndFeelException

fun main() {
    SwingUtilities.invokeLater {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
        } catch (thr: ClassNotFoundException) {
            System.err.println(thr.localizedMessage)
        } catch (thr: InstantiationException) {
            System.err.println(thr.localizedMessage)
        } catch (thr: IllegalAccessException) {
            System.err.println(thr.localizedMessage)
        } catch (thr: UnsupportedLookAndFeelException) {
            System.err.println(thr.localizedMessage)
        }
        TKDupeFinderGUI().isVisible = true
    }
}
```

Not bad, but that's not what we want to pursue further, we want to Compose-ify the ui, right? Take a look at this wireframe:

![Wireframe of TKDupeFinder](https://dev-to-uploads.s3.amazonaws.com/i/7fuonnb0fupjm47ngyiq.png)

So we need some buttons, a label, a list and a textfield. Here is how we could wire things up.

```kotlin
package com.thomaskuenneth.tkdupefinder

import androidx.compose.desktop.Window
import androidx.compose.foundation.ScrollableColumn
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp

fun main() = Window(title = "TKDupeFinder", size = IntSize(600, 400)) {
    MaterialTheme {
        Column() {
            FirstRow()
            SecondRow()
            ThirdRow()
        }
    }
}

@Composable
fun FirstRow() {
    val name = remember { mutableStateOf(TextFieldValue("")) }
    Row(
            modifier = Modifier.fillMaxWidth()
                    .padding(8.dp),
    ) {
        TextField(
                value = name.value,
                placeholder = {
                    Text("Base directory")
                },
                modifier = Modifier.alignBy(LastBaseline)
                        .weight(1.0f),
                onValueChange = {
                    name.value = it
                },
        )
        MySpacer()
        Button(
                onClick = {},
                modifier = Modifier.alignByBaseline(),
        ) {
            Text("Find")
        }
    }
}

@Composable
fun SecondRow() {
    Row(
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier.fillMaxWidth()
                    .padding(8.dp),
    ) {
        Button(onClick = {}) {
            Text("\u140A")
        }
        MySpacer()
        Button(onClick = {}) {
            Text("\u1405")
        }
        MySpacer()
        Text(text = "1 of 42")
    }
}

@Composable
fun MySpacer() {
    Spacer(modifier = Modifier.width(8.dp))
}

@Composable
fun ThirdRow() {
    val scrollState = rememberScrollState()
    ScrollableColumn(
            scrollState = scrollState,
            modifier = Modifier.fillMaxSize().padding(8.dp),
    ) {
        Text("1")
        Text("2")
        Text("3")
    }
}
```

On macOS this looks like this:

![Very first iteration](https://dev-to-uploads.s3.amazonaws.com/i/sla12y7xz7a640lehh6p.png)

Now, this for sure is only a very rough cut. But we will be working on it. So stay tuned.