Custom Title Bar, Skinning, and ViewModel in Kotlin Compose Desktop
This article demonstrates how to implement a custom title bar with window controls, apply dynamic skinning, integrate ViewModel using Precompose, handle network and file I/O, manage multi‑page navigation, open file selectors, and use KV storage in a Kotlin Compose Multiplatform desktop application, with full code examples.
1. Introduction
The previous article introduced the basics of Kotlin + Compose + Multiplatform cross‑platform development, including project setup, configuration, packaging, and basic layout. This second part dives into desktop‑specific implementations.
The topics covered in this article are:
1. Custom title bar with drag, maximize, minimize, etc.
2. Skinning (theme switching).
3. Using ViewModel on the desktop similar to Android.
4. Network requests and file I/O.
5. Multi‑page navigation similar to TabHost and routing.
6. Opening a file chooser.
7. KV storage usage.
Source code repository: https://github.com/wgllss/Kotlin_Compose_Multiplatform_Demo
2. Custom Title Bar, Drag, Maximize, Minimize
To hide the default window title bar, set Window.undecorated = true. Define the minimum window size with:
window.minimumSize = Dimension(JvmConfig.windowWidth, JvmConfig.windowHeight)Control window state:
Maximize: window.placement = WindowPlacement.Maximized Restore: window.placement = WindowPlacement.Floating Minimize: windowState.isMinimized = true Close: ApplicationScope.exitApplication() Make the custom title bar draggable:
scope.WindowDraggableArea(Modifier.fillMaxWidth().height(40.dp)) {
// custom title bar content
}3. Skinning (Theme Switching)
Apply a unified UI theme with:
MaterialTheme(colorScheme = ThemeManager.skinTheme) {
Surface(color = MaterialTheme.colorScheme.background) {
// content
}
}Switch themes by updating a mutableStateOf value:
var skinTheme by mutableStateOf(getColorsSchemes(skinThemeType))Define multiple color schemes and store the selected index using KV storage:
object ThemeManager {
var skinThemeType by mutableStateOf(PlatformKVStore.getSkinType())
var skinTheme by mutableStateOf(getColorsSchemes(skinThemeType))
private fun getColorsSchemes(skinType: Int) = when (skinType) {
0 -> LightColors
1 -> DarkColors
2 -> Colors72
// ... other schemes ...
else -> LightColors
}
}When the user selects a new skin, update the state and persist the index:
PlatformKVStore.saveSkin(it)
ThemeManager.skinTheme = item4. Using ViewModel on Desktop
Add the Precompose libraries:
jvmMain.dependencies {
// Desktop ViewModel support
implementation("moe.tlaster:precompose:1.6.2") // >=1.6.0
implementation("moe.tlaster:precompose-viewmodel:1.6.2") // submodule
}Features include Windows/macOS support, deep integration with ViewModel and MVI, StateFlow composition, lifecycle compatibility, desktop‑specific lifecycle events, and coroutine support for shared desktop and mobile codebases.
5. Network and File I/O
Since the desktop target runs on the JVM, you can reuse the Android networking stack. Use Okhttp + Retrofit for HTTP requests, and standard Java/Kotlin I/O APIs for file operations.
6. Multi‑Page Navigation (TabHost & Routing)
Compose provides Tab + VerticalPager/HorizontalPager for simple tab navigation, or you can use a full router with Precompose:
@Composable
fun NavGraph() {
val navigator = rememberNavigator("key222222")
val viewModel = viewModel { TabViewModel9() }
NavHost(
navigator = navigator,
navTransition = remember {
NavTransition(
createTransition = fadeIn(),
destroyTransition = fadeOut(),
pauseTransition = fadeOut(),
resumeTransition = fadeIn()
)
},
initialRoute = RouterUrls.news_first
) {
scene(RouterUrls.news_first) {
RouterFirst("BA8D4A3Rwangning", viewModel) {
navigator.navigate("${RouterUrls.news_second}?title=${it.title}&docid=${it.docid}")
}
}
scene(RouterUrls.news_second) { backStackEntry ->
val title = backStackEntry.query<String>("title") ?: ""
val docid = backStackEntry.query<String>("docid") ?: ""
RouterSample(navigator, docid, title)
}
}
}7. Opening a File Chooser
Use the native Swing JFileChooser component. Example for selecting a directory:
JFileChooser(PlatformKVStore.getDownloadDir()).apply {
fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
if (showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
val selectedDir = selectedFile.absolutePath
_downloadPath.value = selectedDir
PlatformKVStore.saveDownloadDir(selectedDir)
}
}Other selection modes: JFileChooser.FILES_ONLY (files only) and JFileChooser.FILES_AND_DIRECTORIES (both).
8. KV Storage Usage
Compose desktop can use com.russhwolf:multiplatform-settings:1.2.0 for key‑value storage across Android, iOS, macOS, JS, JVM, and Windows:
val settings = Settings()
settings.putInt("count", 5) // store integer
val value = settings.getInt("count", 0) // read with default9. Summary
This tutorial covered custom title bar implementation, theme switching, desktop ViewModel integration, network and file I/O, multi‑page navigation, file chooser usage, and KV storage in a Kotlin Compose Multiplatform desktop application. The full source code is available on GitHub.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
