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
}
}
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:
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")
}
}
On macOS this looks like this:
Now, this for sure is only a very rough cut. But we will be working on it. So stay tuned.
Top comments (4)
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?
Sorry, almost missed your comment. I accidentally stumbled upon a "builtin" class called
DesktopMaterialTheme
which extends the knownMaterialTheme
. If you take a look at the source they pass aMaterialTheme.colors
which we might be able to replace...ahh pretty neat, thanks <3
The second part of the article is online: dev.to/tkuenneth/from-swing-to-jet...