Search code examples
androidandroid-jetpack-composeandroid-dark-theme

Programmatically switch light/dark theme


I would like to create an isDarkTheme variable whose default value is that of the device's system. Then I would like to create a setter that can be used anywhere in the app. I've written this code which isn't correct:

class ThemeViewModel : ViewModel() {
    private val _isDarkTheme = MutableStateFlow(isSystemInDarkTheme())
    val isDarkTheme: StateFlow<Boolean> = _isDarkTheme.asStateFlow()
    
    fun toggleTheme() {
        _isDarkTheme.value = !_isDarkTheme.value
    }
}

Solution

  • isSystemInDarkTheme() is a composable function, so you cannot call it from your view model.

    Instead change your view model to not only allow the states light (false) and dark (true), but also the state undefined (null):

    class ThemeViewModel : ViewModel() {
        private val _isDarkTheme: MutableStateFlow<Boolean?> = MutableStateFlow(null)
        val isDarkTheme: StateFlow<Boolean?> = _isDarkTheme.asStateFlow()
    
        fun setTheme(isDarkTheme: Boolean) {
            _isDarkTheme.value = isDarkTheme
        }
    }
    

    I replaced toggleTheme with setTheme to make the null handling a bit easier.

    Now you can use the view model in your composable like this:

    val isDarkTheme =
        viewModel.isDarkTheme.collectAsStateWithLifecycle().value ?: isSystemInDarkTheme()
    
    MyAppTheme(isDarkTheme) {
        Button(onClick = { viewModel.setTheme(!isDarkTheme) }) { Text("toggle theme") }
    }
    

    Here you can use isSystemInDarkTheme() as a fallback should the view model's state be in the undefined state.