Performance In Jetpack Compose — Stability & Immutability

Tolga Pirim on 2024-01-16

Performance In Jetpack Compose — Stability & Immutability

Photo by Marc Sendra Martorell on Unsplash

When a state change occurs in Jetpack Compose, it can automatically skip composable functions whose parameters have not changed. This ensures efficient rendering and improves performance. But in some situations it can’t perform this and have to recompose the composable functions.

This article explains why the operation cannot be performed and how to improve the application’s performance.

In this example, there are a Checkbox and a Contact List composable function that shows the contact list.

Using the Layout Inspector tool we can observe which composables are recompose on any state change. When state change, the recomposed elements are highlighted by blinking in blue. At the same time, we can see how many composable functions have been recomposed and how many have been skipped in the Layout Inspector.

We can open Layout Inspector from Tools/Layout Inspector in Android Studio.

In this example, we change only the state of Checkbox, however the Contact List composable have recomposed four times although never change parameters of the Contact List.

Restartable & Skippable In Jetpack Compose Functions

Before explaining the concepts of restartable and skippable, I’ll first explain what recomposition is.

Recomposition is the process of calling your composable functions again when inputs change. This happens when the function’s inputs change. When Compose recomposes based on new inputs, it only calls the functions or lambdas that might have changed, and skips the rest. — Android Documentation

The Compose compiler marks a function as restartable and skippable to determine whether it is skippable or restartable.

What is the Restartable and Skippable?

Restartable

Marking a function as restartable indicates that it can be called again if any changes occur in its inputs.

Skippable

Marking a function as skippable, it can be skip calling the function if the parameters haven’t changed since last call.

How Do We Know The Function Has Marked a Restartable and/or Skippable?

Enable the report of the Compose Compiler Metrics

Write the script above to the build.gradle.kts (Project) file.

To get the result of the report, we can run this command,

./gradlew assembleRelease -PcomposeCompilerReports=true

This task will generate four files in the build directory of each module under the compose_compiler file.

  1. app_release-classes.txt -> A report on the stability of classes
  2. app_release-composables.txt -> A report on the restartability and skippability of the composables.
  3. app_release-composables.csv -> A csv version of the above text file.
  4. app_release-module.json -> Contains some overall stats.
restartable scheme("[androidx.compose.ui.UiComposable]") fun ContactList(
  unstable contactListState: ContactListState
)  

This ContactList composable is restartable but not skippable. Why?

The ContactListState parameter has marked unstable. What does unstable mean in terms of stability, and how can I make it stable?

Stability

The stability of a composable’s parameters is crucial in determining whether it needs to be re-evaluated during recomposition in Compose.

Stable Parameters

Since standard Collection classes are defined as interfaces in Kotlin, their underlying implementation may be unstable. The Compose compiler can’t be sure of the immutability of this class since it only sees the declared type, and will mark it as unstable.

Let’s see our ContactListState data class. To see the report open up app_release-classes file.

unstable class ContactListState {
  unstable val names: List<String>
  stable val isLoading: Boolean
  <runtime stability> = Unstable
}

As we mentioned above the list parameters is unstable. If a data class has unstable parameters, it will also be unstable.

Stable and Immutable Annotations

Immutable —As the name suggests, these hold data that is immutable. Since the data never changes, Compose can treat this as stable data.

@Immutable
data class ContactListState(
    val names: List<String>,
    val isLoading: Boolean = false,
)

Stable — These hold data that is mutable but notify Composition upon mutating.

@Stable
data class ContactListState(
    val names: List<String>,
    val isLoading: Boolean = false,
)

We could use either one. After making this change, let’s rerun the metrics and observe any changes.

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun ContactList(
  stable contactListState: ContactListState
)

The ContactList composable is both restartable and skippable. And ContactList’s parameters is stable.

When the state changes, only CheckBox is recompose. Since any of the Contact List parameters has not changed and is stable, Compose can safely skip it.

Connect me:

GithubLinkedin

Resources