Stability and Skippability
In jetpack Compose stability, not triggering recomposition when inputs of a function hasn't changed, is achieved by using immutable variables or/and using @Immutable or @Stable annotation to make functions skippable as can be seen in detail in articles below.
https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8 https://chrisbanes.me/posts/composable-metrics/
Skippable — when called during recomposition, compose is able to skip the function if all of the parameters are equal with their previous values.
And types can be
Types could be immutable or stable:
Immutable — Indicates a type where the value of any properties will never change after the object is constructed, and all methods are referentially transparent. All primitive types (String, Int, Float, etc) are considered immutable.
Stable — 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.
@Immutable
is pretty straightforward and not part of this question.
@Stable Annotation and Questions
Stable is used to communicate some guarantees to the compose compiler about how a certain type or function will behave. When applied to a class or an interface, Stable indicates that the following must be true:
- The result of equals will always return the same result for the same two instances.
- When a public property of the type changes, composition will be notified.
- All public property types are stable.
When applied to a function or a property, the Stable annotation indicates that the function will return the same result if the same parameters are passed in. This is only meaningful if the parameters and results are themselves Stable, Immutable, or primitive.
In first article by Ben Trengrove it's shown as
@Stable
class MyStateHolder {
var isLoading by mutableStateOf(false)
}
However, a class that only holds stable MutableState
is already stable, what's the point of using @Stable
annotation here?
A class i created
class MyUiState {
var counter by mutableStateOf(0)
var text by mutableStateOf("")
var items by mutableStateOf(listOf(1, 2, 3))
}
logs
stable class MyUiState {
stable var counter$delegate: MutableState<Int>
stable var text$delegate: MutableState<String>
stable var items$delegate: MutableState<List<Int>>
<runtime stability> = Stable
}
When i add a public or private unstable param, a List, it becomes unstable
class MyUiState(
var list: List<Int> = listOf()
) {
var counter by mutableStateOf(0)
var text by mutableStateOf("")
var items by mutableStateOf(listOf(1, 2, 3))
}
logs
unstable class MyUiState {
unstable var list: List<Int>
stable var counter$delegate: MutableState<Int>
stable var text$delegate: MutableState<String>
stable var items$delegate: MutableState<List<Int>>
<runtime stability> = Unstable
}
When i add @Stable
annotation it becomes stable but
Doesn't adding an unstable public param already violate item number 3, and the contract? As i checked it doesn't recompose without changing param but definition says it should not be, right?
- All public property types are stable.
When applied to a function or a property, the Stable annotation indicates that the function will return the same result if the same parameters are passed in. This is only meaningful if the parameters and results are themselves Stable, Immutable, or primitive.
Also in first article mentions
You should be very careful about the usage of these annotations as overriding the compiler behavior could lead you to unforeseen bugs should you get it wrong. If it is possible to make your class stable without an annotation, you should strive to achieve stability that way.
Is there any example that @Stable
annotation is used incorrectly and could lead potential bugs? If possible an example with @Immutable
would be nice to see how wrong usage breaks recomposition.