Stability in Jetpack Compose & Avoid Recomposition

Emre Memil
4 min readOct 5, 2023

--

Photo by Guido Coppa on Unsplash

After Jetpack Compose entered the lives of Android developers, our UI work became much easier. However, in some cases, if you do not have enough knowledge about Jetpack Compose, it can sometimes make your life miserable. When creating your UIs with Jetpack compose, you should not miss some things for your application performance and pay close attention to them. Recompositon is one of the most important issue you should not skip. Let’s talk a little about recompositon.

What is Recompositon?

Recompositon in Jetpack Compose refers to the process of re-evaluating and potentially re-invoking composable functions in response to changes in their input parameters or external state. This is a fundamental concept in Compose and is what enables the UI to automatically update when underlying data or state changes.

Android’s official site talks about recomposition as follows:

Recomposition is when Jetpack Compose re-executes the composables that may have changed in response to state changes, and then updates the Composition to reflect any changes.

To get rid of Recompositon, let’s take a look at Compose’s Stability concept..

Stability in Jetpack Compose

In Compose, the stability of a composable’s parameters plays a crucial role in deciding whether the composable needs to be re-evaluated during recomposition:

Stable parameters:

  • If a composable has Stable parameters that have not changed, Compose skips it.
  • Stable parameters are the values passed to a composable function that typically do not change.
  • If a composable function has stable parameters and their values remain unchanged, Compose can optimize by skipping the re-evaluation of this composable function.
  • For example; String, Int, Float, ImmutableList types are stables parameters

Unstable parameters:

  • If a composable has Unstable parameters, Compose always recomposes it when it recomposes the component’s parent.
  • Unstable parameters are values that tend to change based on external state or data.
  • If a composable function has unstable parameters and these values change, Compose will always re-evaluate this function. This means that the composable function needs to be updated every time the recomposition process occurs.
  • For example; List, Map, Set types are unstable parameters. Also var declarations on Data classesis unstable.

Let’s see this better with an example:

Let’s create a data class called Contact and use var declaration in one field.

data class Contact(var name: String, val number: String)

Now let’s create our UI

@Composable
fun ContactView(contact: Contact, modifier: Modifier = Modifier) {
var selected by remember { mutableStateOf(false) }

Column(modifier) {
ContactDetails(contact)
ToggleButton(selected, onStateChanged = { selected = !selected })
}
}
@Composable
fun ToggleButton(selected: Boolean, onStateChanged: (Boolean) -> Unit) {
Switch(checked = selected, onCheckedChange = {
onStateChanged(it)
})

Log.d("Stability", "ToggleButton view composed")
}
@Composable
fun ContactDetails(contact: Contact) {
Text(text = "Name: ${contact.name}, Number: ${contact.number}")

Log.d("Stability", "ContactDetails view composed")
}

Every time the value of the toggle button changes we will see the logcat like this.

Stability->ContactDetails view composed
Stability->ToggleButton view composed
Stability->ContactDetails view composed
Stability->ToggleButton view composed
Stability->ContactDetails view composed
Stability->ToggleButton view composed
Stability->ContactDetails view composed
Stability->ToggleButton view composed

Unstable Situation

As you can see, even though the contact object has not changed, ContactDetails view has been recomposed. Because we used var declaration in the Contact data class. Therefore, Jetpack compose could not guarantee that this parameter had not changed. Thus, ContactDetails view re-created.

Now let’s change all declarations in the Contact data class to val.

data class Contact(val name: String, val number: String)

Now let’s change the value of the toggle button a few times and take a look at logcat.

Stability->ContactDetails view composed
Stability->ToggleButton view composed
Stability->ToggleButton view composed
Stability->ToggleButton view composed
Stability->ToggleButton view composed
Stability->ToggleButton view composed
Stability->ToggleButton view composed

Stable Situation

As you can see, the ContactDetails view was created only once and was never recomposed. Because we always used val in data class. Therefore, Jetpack compose guaranteed that this parameter will not change. And the recomposition situation did not happen.

Stable Annotation & Immutable Annotation

Let’s say we have a var declaration in our Data class, but if we are sure that it will not change and we do not want compose to be recomposition, we can use the “@Stable” annotation or “@Immutable” annotation for this example… I will explain these annotations in detail later.

@Stable
data class Contact(var name: String, val number: String)

So, if we update our Contact data class like this, even though we use the var declaration, the recomposition situation will not occur and our logcat output will be as follows.

Stability->ContactDetails view composed
Stability->ToggleButton view composed
Stability->ToggleButton view composed
Stability->ToggleButton view composed
Stability->ToggleButton view composed
Stability->ToggleButton view composed
Stability->ToggleButton view composed

Stable Annotation

If you use “@Stable”, you are promising that the value is an observable and that listeners will be notified if it changes.

For example, by using “@Stable” in the data class definition below, compose is recreated only when the value of the flag variable changes.

@Stable
data class Contact(var flag: MutableState<Boolean> = mutableStateOf(false))

Immutable Annotation

If you use “@Immutable”, you are making a promise that the value will never change.

For example, even if you add or delete an item to the list, the compose will never be recreated because you use “@Immutable”

@Immutable
data class Contact(var list: ArrayList<String>)

Yeah! Actually, it’s that simple. Recomposition is one of the vital issues in Jetpack Compose. If you have read this article until here, you can now stay “@Stable” in compose.

Source: https://developer.android.com/jetpack/compose/performance/stability

--

--