From Swing to Compose Desktop (7 Part Series)
1 From Swing to Jetpack Compose Desktop #1
2 From Swing to Jetpack Compose Desktop #2
… 3 more parts…
3 From Swing to Compose Desktop #3
4 From Swing to Compose Desktop #4
5 From Swing to Compose Desktop #5
6 From Swing to Compose Desktop #6
7 From Swing to Compose Desktop #7: Concurrency
Welcome to the third post about my journey of transforming a Java Swing app to Compose for Desktop. Today I will cover the actual search for duplicates. Before we start: I found out that the official name is Compose for Desktop with no Jetpack prefix. I left the previous posts unchanged, but from now on will use the correct name.
The old Swing user interface calls the business logic like this:
public void setupContents() {
df.clear();
df.scanDir(textfieldBasedir.getText(), true);
df.removeSingles();
checksums = df.getChecksums();
updateGUI();
}
Enter fullscreen mode Exit fullscreen mode
df
and checksums
are simple member variables.
private TKDupeFinder df = new TKDupeFinder();
private String[] checksums = {};
Enter fullscreen mode Exit fullscreen mode
The Swing code for updateGUI()
looks lie this:
private void updateGUI() {
boolean enabled = checksums.length > 1;
buttonPrev.setEnabled(enabled);
buttonNext.setEnabled(enabled);
currentPos = 0;
updateContents(0);
}
Enter fullscreen mode Exit fullscreen mode
To understand what’s going on, please recall how the old app looks like:
Files are assumed duplicates if they share the same MD5 hash. That’s what is stored in checksums
. buttonPrev
and buttonNext
represent the small arrow buttons, which allow you to browse through the checksums. Each checksum refers to a list of File
s. The mapping takes place in a method called updateContents()
.
private void updateContents(int offset) {
modelFiles.removeAllElements();
if (checksums.length < 1) {
labelInfo.setText("keine Dubletten gefunden");
} else {
currentPos += offset;
if (currentPos >= checksums.length) {
currentPos = 0;
} else if (currentPos < 0) {
currentPos = checksums.length - 1;
}
List<File> files = df.getFiles(checksums[currentPos]);
files.stream().forEach((f) -> {
modelFiles.addElement(f);
});
labelInfo.setText(Integer.toString(currentPos + 1) + " von "
+ Integer.toString(checksums.length));
}
listFiles.getSelectionModel().setSelectionInterval(1, modelFiles.getSize() - 1);
updateButtons();
}
Enter fullscreen mode Exit fullscreen mode
So how does this translate to our new Kotlin code?
A very important variable is df
. For the sake of simplicity I declare it top-level:
private val df = TKDupeFinder()
Enter fullscreen mode Exit fullscreen mode
We also need to remember
two new states, currentPos
and checksums
. Just like name
I put them in the TKDupeFinderContent
composable:
val currentPos = remember { mutableStateOf(0) }
val checksums = remember { mutableStateOf<List<String>>(emptyList()) }
Enter fullscreen mode Exit fullscreen mode
The are passed to some of my other composables, sometimes as a state (when that composable must alter the value), sometimes just the value (when it is used to display something). You may be asking why, regarding checksums
, I do not just remember
a mutable list and change its contents. That’s because the old business logic returns a list after a search, so it is easier to replace the reference rather than update the mutable list by removing the old and adding the new contents.
FirstRow(name, currentPos, checksums)
SecondRow(currentPos, checksums.value.size)
ThirdRow(currentPos.value, checksums.value)
Enter fullscreen mode Exit fullscreen mode
Now, let’s take a look at the composables. For the sake of readability I omit some unchanged code.
@Composable
fun FirstRow(name: MutableState<TextFieldValue>,
currentPos: MutableState<Int>,
checksums: MutableState<List<String>>) {
Row( … ) {
…
Button(
onClick = {
df.clear()
df.scanDir(name.value.text, true)
df.removeSingles()
currentPos.value = 0
checksums.value = df.checksums.toList()
},
modifier = Modifier.alignByBaseline(),
enabled = File(name.value.text).isDirectory
) {
Text("Find")
}
}
}
Enter fullscreen mode Exit fullscreen mode
I guess the most interesting part here is inside onClick()
. The search logic remains unchanged (invoking clear()
, scanDir()
and removeSingles()
. But through changing currentPos
and checksums
I can nicely trigger a ui refresh.
Next is SecondRow
:
@Composable
fun SecondRow(currentPos: MutableState<Int>, checksumsSize: Int) {
val current = currentPos.value
Row( … ) {
Button(onClick = {
currentPos.value -= 1
},
enabled = current > 0) {
Text("\u140A")
}
MySpacer()
Button(onClick = {
currentPos.value += 1
},
enabled = (current + 1) < checksumsSize) {
Text("\u1405")
}
MySpacer()
Text(text = if (checksumsSize > 0) {
"${currentPos.value + 1} of $checksumsSize"
} else "No duplicates found")
}
}
Enter fullscreen mode Exit fullscreen mode
currentPos
is passed as a state, because button clicks need to alter it, whereas checksumsSize
is not changed but used only for checks and output.
Finally, ThirdRow
.
Until today the list simply showed three fixed texts. Now I present the duplicates like this:
@Composable
fun ThirdRow(currentPos: Int, checksums: List<String>) {
val scrollState = rememberScrollState()
ScrollableColumn(
scrollState = scrollState,
modifier = Modifier.fillMaxSize().padding(8.dp),
) {
if (checksums.isNotEmpty())
df.getFiles(checksums[currentPos]).forEach {
Text(it.absolutePath)
}
}
}
Enter fullscreen mode Exit fullscreen mode
Here, too, both arguments do not represent a remembered state but its value
, because they are not altered.
This is how the app looks now:
We for sure can beautify the visuals of the list. That’s a topic for a future post. The next thing I will cover is list handling. The old app has two buttons to view or delete duplicate files. I am curious how I will map this behavior to Material Design. So please stay tuned.
From Swing to Jetpack Compose Desktop #1
From Swing to Jetpack Compose Desktop #2
From Swing to Compose Desktop (7 Part Series)
1 From Swing to Jetpack Compose Desktop #1
2 From Swing to Jetpack Compose Desktop #2
… 3 more parts…
3 From Swing to Compose Desktop #3
4 From Swing to Compose Desktop #4
5 From Swing to Compose Desktop #5
6 From Swing to Compose Desktop #6
7 From Swing to Compose Desktop #7: Concurrency
暂无评论内容