Thread
#compose
    l

    Lilly

    11 months ago
    I have a data class annotated with
    @Stable
    and would expect that a change to the property would reflect the new state but it doesn't:
    @Stable
    data class StableState(var isChecked: Boolean)
    
    @Composable
    fun StableTest(state: StableState) {
        Checkbox(checked = state.isChecked, onCheckedChange = {
            state.isChecked = it
        })
    }
    What did I miss?
    m

    mkrussel

    11 months ago
    Your class is not actually stable. https://developer.android.com/reference/kotlin/androidx/compose/runtime/Stable You are not meeting requirement 2. Any writeable property in your
    Stable
    class should get reported back to compose through a compose state object.
    l

    Lilly

    11 months ago
    ok I guess I don't understand the second point. Doesn't it mean that when the property changes, recomposition is triggered?
    Alex Vanyo

    Alex Vanyo

    11 months ago
    A version of
    StableState
    that is stable with the same interface could be:
    @Stable
    class StableState(isChecked: Boolean) {
        var isChecked by mutableStateOf(isChecked)
    }
    m

    mkrussel

    11 months ago
    It means that you have a
    State
    variable in your object that changes when that property changes. I believe the state can be private and it will still work. Not sure if it is possible to have a stable non immutable data class
    c

    Casey Brooks

    11 months ago
    Compose doesn't automatically augment your classes to make them reactive, it still needs you, the programmer, to tell Compose which variables should be reactive. Replacing
    var
    with
    val mutableStateOf
    is the main way to make a property able to notify Compose of changes
    It would not be possible to have a mutable data class be
    Stable
    , unless you override the implementation of
    equals/hashcode
    . You could change the property of one instance and make it not equal to another that it was previously equal to (thus breaking rule #1 of
    @Stable
    )
    l

    Lilly

    11 months ago
    Ahh ok, but there is one thing I still don't understand. How does some of Compose internal state classes work, e.g.
    @Stable
        private class SnackbarDataImpl(
            override val message: String,
            override val actionLabel: String?,
            override val duration: SnackbarDuration,
            private val continuation: CancellableContinuation<SnackbarResult>
        ) : SnackbarData { ... }
    or
    @Stable
    class DrawerState(
        initialValue: DrawerValue,
        confirmStateChange: (DrawerValue) -> Boolean = { true }
    ) { .. }
    I see here primitives not
    State
    objects.
    c

    Casey Brooks

    11 months ago
    You're free to use normal variables (val or var), and Compose will always read them just fine the first time. But notice that the properties in
    SnackbarDataImpl
    are all vals, meaning the class is immutable. Compose has no need of ever recomposing on any particular property in the class. Since nothing in the class could ever change, the corresponding UI will always be the same. To actually update the UI with a new snackbar, you'd replacce the old instance with a new instance, which Compose would detect as a change and then update the corresponding parts of the UI as a result. But Compoe will only know that there's a new Snackbar when a
    mutableStateOf
    has that new snackbar instance set to it.
    l

    Lilly

    11 months ago
    Ahhh I got it. Thanks Casey and the other guys ❤️
    c

    Casey Brooks

    11 months ago
    The snackbar seems slightly different, since you're setting the data to a "handler", but behind-the-scenes the handler is just forwarding the snackbar data to a different part of the UI tree (the snackbar host), where it reads a
    State
    variable and displays the actual snackbar data as anything else would. The same pattern holds for scrolling, ripples, genreic animatioons, and everything else. The data behind the whole of Compose is always immutable at the data class level, or else uses
    mutableStateOf
    to make sure Compose knows when something changes
    It definitely takes a different way of thinking about UIs to get comfortable with Compose, but it's so much easier to work with compareed to traditional UIs once you wrap your head around it. The "Thinking in Compose" and "Managing State" documentation articles are a very good starting point for this https://developer.android.com/jetpack/compose/mental-model https://developer.android.com/jetpack/compose/state
    l

    Lilly

    11 months ago
    Yeah I should have had a look into the compose-api-guidelines. Thanks for the insights
    This clarifies it
    @Stable
     when applied to a type indicates a type that is mutable, but the Compose runtime will be notified if and when any public properties or method behavior would yield different results from a previous invocation. Such a type may only back its properties using other 
    @Stable
     or 
    @Immutable
     types.
    and
    MutableState
    is such a
    @Stable
    type:
    @Stable
    interface MutableState<T> : State<T>