androidiosdesktopkotlin-multiplatformcompose-multiplatform

How to react to changes in desktop system theme in KMP to change color scheme?


I am working on a Kotlin Multiplatform App with Compose Multiplatform for targets android, iOS and Desktop (MacOS, Linux and Windows with jvm). In my App.kt, I am setting the color scheme on the app based on the system theme, using light and dark color schemes as corresponds. Both Android and iOS are working as expected. However, in Windows and MacOS (I'm assuming Linux as well, but I've not been able to test it) it is working for the system theme the device had on the app start, but it doesn't change if the system config changes, meaning that the app has to be restarted in order to react to this change. How can I handle that? I have added some jvmArgs to adapt to the system theme, but it is only changing the top bar of the window of the app, not the scheme of the app itself.

compose.desktop {
    ...
    application {
        ...
        nativeDistributions {
            ...
            jvmArgs("-Dapple.awt.application.appearance=system")
        }
    }
}
@Composable
fun App() {
    val isDarkTheme = isSystemInDarkTheme()

    MaterialTheme(
        colorScheme = if(isDarkTheme) darkColorScheme() else lightColorScheme(),
        typography = sampleTypography(),
    ) {
      AppContent()
    }
}

Solution

  • It's a known issue. Until it's fixed, you would have to use a library to detect theme changes on the desktop.

    Personally I use jSystemThemeDetector, here's what the wrapper looks like.

    You can have it as actual for desktop and keep using system variant for other platforms.

    import androidx.compose.foundation.isSystemInDarkTheme
    import androidx.compose.runtime.mutableStateOf
    import androidx.compose.runtime.Composable
    import androidx.compose.runtime.DisposableEffect
    import androidx.compose.runtime.remember
    import com.jthemedetecor.OsThemeDetector
    import java.util.function.Consumer
    
    @Composable
    fun isSystemInDarkTheme(): Boolean {
        val isSystemInDarkTheme = isSystemInDarkTheme().let { currentValue ->
            remember(currentValue) { mutableStateOf(currentValue) }
        }
        DisposableEffect(isSystemInDarkTheme) {
            val listener = Consumer<Boolean> {
                isSystemInDarkTheme.value = it
            }
            val detector = OsThemeDetector.getDetector()
            detector.registerListener(listener)
            onDispose {
                detector.removeListener(listener)
            }
        }
        return isSystemInDarkTheme.value
    }