Performance Optimization in Jetpack Compose

Bharat Kumar
4 min readDec 27, 2023
This is what you will hear if you don't read this article.

We know how Compose has developed and is the future of UI development in Android and we can see that in KMP with compose multiplatform in the run.

Here I am bringing some tips to optimize performance in Compose

1. Use derivedStateOf

Most of you don't use this but derivedStateOf can reduce a lot of your recompositions.

This is useful when the state changes rapidly but doesn’t affect your composable. Still your composable will recompose with every change in the state. But if you keep that in derivedStateOf when there is no change in the required value it won’t recompose

Example — Let's say you want to validate whether your password is Valid or not while writing it. If derivedStateOf is not used then the composable will recompose with every change in the letter but with the use of derivedStateOf the the composable will only recompose when the boolean isValid changes.

var username by remember { mutableStateOf("") }
val isValidPassword = isUsernameValid(username)
OutlinedTextField(value = username, onValueChange = {username = it} )
Button(onClick = {}, enabled = isValidPassword) {
Text(text = "Submit")
}

This is an example of bad usage here we can update the above code with derivedStateOf to increase performance.

var username by remember { mutableStateOf("") }
val isValidPassword by remember {
derivedStateOf { isUsernameValid(username) }
}
OutlinedTextField(value = username, onValueChange = {username = it} )
Button(onClick = {}, enabled = isValidPassword) {
Text(text = "Submit")
}

Here by adding derivedStateOf, we may reduce a lot of unnecessary recompositions.

2. Use keys in LazyLists

This is a simple small change that can increase your LazyColumn /LazyRow performance.

To add a key you just need to pass a unique ID to the key

This works almost like how DiffUtil works for a Recycler View and we know how drastic an increase in performance RecyclerView gets with DiffUtils

LazyRow(){
items(items = listOfContact , key = {
it.id
}
) {
Text(text = it.name)
}
}}

3. Use immutable values as much as possible in Data classes

When you have var variables in Data classes then compose thinks it can change as var is mutable so during recompositions compose thinks as the value is mutable the value may have changes and on the flow, it recomposes them even though the value has not changed. It doesn't happen every time but do happen sometimes while recomposing.

The same thing happens with mutable collections such as List or ArrayList. To mitigate this, consider using their immutable counterparts, such as ImmutableList<T>, to ensure accurate recomposition based on actual value changes.

4. Use @Stable or @Immutable on data classes containing var.

As I mentioned in the last point making use of val or Lists can cause unnecessary recompositions but you may have to keep them for some purpose now what can you do or let's say you have a list in a data class now what?

There is a way to tell compose that this data class is stable and it will not change with @Stable or @Immutable annotation.

⚠️ Warning: It is very important to note that this is a contract to follow the corresponding rules for the annotation. It does not make a class Immutable/Stable on its own. Incorrectly annotating a class could cause recomposition to break.

@Immutable
data class Snack(
val id: Long,
val name: String,
val imageUrl: String,
val price: Long,
var tagline: String = ""
)

@Stable
data class Snack(
val id: Long,
val name: String,
val imageUrl: String,
val price: Long,
var tagline: String = ""
)

Here by adding these annotations, you can tell compose that this class is stable.

However, don't use this until it's too necessary as this is not the correct way to handle things use this only as a last resort do use the above steps first and keep the class immutable as much as possible.

5. Use lightweight images in LazyLists.

Using lightweight images in a LazyColunm or LazyRow will boost your performance greatly. Heavy images are a major cause of most lags in Lazy Lists in Compose.

You can use Coil or some image caching library also to increase performance however most of you already use it.

If you can pay then you can use something like https://imagekit.io/.

6. Usage of Modifier.clickable() always leads to recomposition

If we use a Modifier.clickable() even in a Text it will recompose every time even when the state or the value does not change. This is the reason Buttons recompose every time even if it's not clicked.

The solution is to wrap your click Modifier in a remember block as shown below. This solves the problem. I also have checked it.

val click = remember {
Modifier.clickable {
// Your Click Event
}
}

Box(modifier = Modifier.then(click))
No one can say you this after reading the article

I hope this article is helpful. If you think something is missing, have questions, or would like to give feedback, go ahead and leave a comment below. I’d appreciate the feedback.

Thank You and have a Happy coding Journey. :)

You can connect with me at LinkedIn — Bharat Kumar

--

--