DEV Community

loading...
Cover image for From Swing to Jetpack Compose Desktop #1

From Swing to Jetpack Compose Desktop #1

tkuenneth profile image Thomas Kuenneth Updated on ・3 min read

Recently, JetBrains announced 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 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:

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

On macOS this looks like this:

Very first iteration

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

Discussion (4)

pic
Editor guide
Collapse
keogami profile image
keogami

The code looks and feels great (no pun intended... maybe >~<)
but, where are they getting their color from?
Edit: now that I think about it, the question should be: can we style the components?

Collapse
tkuenneth profile image
Thomas Kuenneth Author

Sorry, almost missed your comment. I accidentally stumbled upon a "builtin" class called DesktopMaterialTheme which extends the known MaterialTheme. If you take a look at the source they pass a MaterialTheme.colorswhich we might be able to replace...

Collapse
keogami profile image
keogami

ahh pretty neat, thanks <3

Collapse
tkuenneth profile image
Thomas Kuenneth Author

The second part of the article is online: dev.to/tkuenneth/from-swing-to-jet...