https://kotlinlang.org logo
l

Lilly

10/07/2021, 6:47 PM
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:
Copy code
@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

10/07/2021, 6:59 PM
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.
☝️ 1
l

Lilly

10/07/2021, 7:09 PM
ok I guess I don't understand the second point. Doesn't it mean that when the property changes, recomposition is triggered?
a

Alex Vanyo

10/07/2021, 7:11 PM
A version of
StableState
that is stable with the same interface could be:
Copy code
@Stable
class StableState(isChecked: Boolean) {
    var isChecked by mutableStateOf(isChecked)
}
m

mkrussel

10/07/2021, 7:11 PM
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

10/07/2021, 7:15 PM
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
☝🏻 1
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

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

Casey Brooks

10/07/2021, 7:23 PM
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 `val`s, 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.
👍 1
l

Lilly

10/07/2021, 7:25 PM
Ahhh I got it. Thanks Casey and the other guys ❤️
c

Casey Brooks

10/07/2021, 7:28 PM
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

10/07/2021, 7:35 PM
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:
Copy code
@Stable
interface MutableState<T> : State<T>
💯 1