Jetpack Compose list performance optimization

https://habr.com/ru/post/645799/image

I once stumbled upon a rather interesting behavior of composable functions in lists, then an article from a medium and reading a book on compose internals helped me, I will attach links to them at the end of the article. Decided to share, maybe it will help someone)

Displaying lists is a common case for most applications, and often these lists are dynamic, changing content, element positions, and so on. There is a pretty quick way to optimize your list in compose.
But first, let’s look at what a “call site” is, a composable function.

Composable

Each function marked with @Composable annotation will have additional information for the kotlin compiler that it emits a UI. When the compiler iterates over this function, it adds information about where it was called, this data is a unique identifier for the composable function.

What is a call site

The call site information is needed for reuse during the recomposition stage of the composable function tree.

@Composable
fun Example(
    firstText: String,
    secondText: String
) {
    Column() {
        Text(firstText) // место вызова для composable с firstText
        Text(secondText) // место вызова для composable с secondText
    }
}

Since both composable functions are called in different places, each of them has its own unique call site identifier.
But let’s look at this example:

@Composable
fun Loop() {
    Column {
        for (num in 1..0) {
            Text(num.toString())
        }
    }
}

You can notice that now the call site is always the same, this violates the unique identifier rule. In such cases, by default, the Kotlin compiler adds to the information about the call site also a position in the list. This allows composable functions to be reused during the recomposition process.

But we’re in trouble on Andryukha’s horses, we have a crime.
Let’s imagine that the list is modifiable. What happens if a new element is added to the beginning of the list? The call site of each composable function created depended on the element’s position in the list, since all positions have changed, now no composable function evaluation performed can be reused, although the content did not change. This behavior should be corrected.

Key optimization

Let’s provide unique information for the call site instead of the compiler

@Composable
fun Loop() {
    Column {
        for (someObj in ourList) {
            key(someObj.key) {
                Text(someObj)
            }
        }
    }
}

Now the call site still has the information that we provide via key. In such a case, when a new element is added to the beginning of the list, a new composable function is initialized only for this item, all other emitted composable functions will be reused.
We can also add this optimization for LazyColumn and LazyRow:

@Composable
fun Loop() {
    LazyColumn(ourList, {someObject -> someObject.id}){
        Text(someObj)
    }
}

List of inspirations:

Article covering this topic in English
Useful book on compose internals

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *