How can I make custom classes/functions trigger re...
# compose
m
How can I make custom classes/functions trigger recompositions? I have a custom multiplatform project to store/load settings like
val theme by settings.bound<AppTheme>(default=MaterialAppTheme(isSystemInDarkTheme()))
but if I change this value elsewhere in the code, I need to tell this part to reload and trigger a recomposition. How would I do this? I tried reading the source for MutableState but didn't quite understand how that triggered recompositions.
m
Copy code
in viewmodel

private val _isDarkModeEnabled = MutableStateFlow(false)
val isDarkModeEnabled = _isDarkModeEnabled.asStateFlow()

in compose file

 val isDarkModeEnabled by settingViewModel.isDarkModeEnabled.collectAsState(isSystemInDarkTheme())
m
so any changes to
settingViewModel.isDarkModeEnabled
will trigger a recomposition?
m
yes
m
So would the following work?
Copy code
@Suppress("UNCHECKED_CAST")
class BoundSetting<T>(private val settings: Settings, private val key: String?, private val default: T?, private val type: KType, private val serializer: KSerializer<T>) {
    private val flow = MutableStateFlow(default)

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        @Suppress("IMPLICIT_CAST_TO_ANY")
        val res = when (type) {
            String::class -> settings.getStringOrNull(key ?: property.name)
            Int::class -> settings.getIntOrNull(key ?: property.name)
            Long::class -> settings.getLongOrNull(key ?: property.name)
            Float::class -> settings.getFloatOrNull(key ?: property.name)
            Double::class -> settings.getDoubleOrNull(key ?: property.name)
            Boolean::class -> settings.getBooleanOrNull(key ?: property.name)
            else -> settings.getStringOrNull(key ?: property.name)?.let { Json.decodeFromString(serializer, it) }
        }

        return (res ?: default) as T
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
        flow.value = value

        if (value == null) {
            settings.remove(key ?: property.name)
            return
        }

        when (type) {
            String::class -> settings.putString(key ?: property.name, value as String)
            Int::class -> settings.putInt(key ?: property.name, value as Int)
            Long::class -> settings
            Float::class -> settings
            Double::class -> settings
            Boolean::class -> settings
            else -> settings.putString(key ?: property.name, Json.encodeToString(serializer, value))
        }
    }

    @Composable
    fun asComposeState() = flow.collectAsState(default)

    companion object {
        inline operator fun <reified T> invoke(settings: Settings, key: String?, default: T): BoundSetting<T> {
            return BoundSetting(settings, key, default, typeOf<T>(), serializer())
        }

        inline operator fun <reified T> invoke(settings: Settings, key: String?): BoundSetting<T?> {
            return BoundSetting(settings, key, null, typeOf<T>(), serializer())
        }
    }
}
Or would this give the wrong value if
asComposeState
is called after
setValue
?
m
in viewmodel
Copy code
fun changeDarkMode(isEnabled: Boolean) = viewModelScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
    appPreferencesImpl.changeDarkMode(isEnabled)
    _isDarkModeEnabled.value = isEnabled
}
m
What's viewmodel?
m
Oh I'm using compose multiplatform, not android
pretty much all of my code is just @Composable functions, with some classes like the one above for handing platform abstractions
m
view model also use compose multiplatform
m
So do I make my BoundSetting extend ViewModel? never mind, ViewModel doesn't exist in Jetbrains Compose
m
first learn what is viewmodel so idea how can solve this problem
BoundSetting not understand what is do
m
IDEA only suggests the one from android lifecycle, which doesn't exist on Desktop or Web
m
feed channel check i send some it compose multiplatform so you have idea how can solve this problem