3

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:

  1. The result of equals will always return the same result for the same two instances.
  2. When a public property of the type changes, composition will be notified.
  3. 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?

  1. 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.

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Also, why Color class is annotated as `@Stable` instead of `@Immutable` since it doesn't have immutable params? – Thracian Jul 12 '23 at 06:49

1 Answers1

1

When you add an unstable public property to a @Stable class, it does indeed violate item number 3 of the @Stable contract, which states that "All public property types are stable." However, the @Stable annotation is not intended to enforce immutability or stability on properties; rather, it serves as an indication to Compose about the stability of the class as a whole.

While the addition of an unstable property might violate the contract, Compose still allows you to mark the class as @Stable. This means that Compose will assume the class is stable and may optimize based on that assumption. However, if the unstable property changes, Compose might not be notified, potentially leading to incorrect behavior or UI glitches.

Also, the decision to use @Stable for the Color class is a design choice to leverage Compose's recomposition optimizations effectively while acknowledging that UI elements using colors may have mutable states and behavior. It allows Compose to optimize based on the stability of the UI elements while still accommodating the potential for changes in color values based on the state.