Search code examples
androidkotlinandroid-jetpack-composekotlin-coroutinescoil

Laggy Lazy Column Android Compose


I've created a whole app in Jetpack Compose. However, the performances on the Lazy Column are pretty bad and it does not make any sense. Lazy Column should be the substitution of RecyclerView, but RecyclerView works much better at the moment.

I made a Lazy Column with headers and Lazy Rows as items (basically a nested list). As you can see there are images but I used the Coil library so everything should be loaded in a separate thread. I've already seen these discussions: link1, link2. But it seems like there's not a solution to this problem, even though now Jetpack Compose is stable.

Did any of you find a way to get better performances or should I substitute this Lazy Rows with a RecyclerView?

Here's a screen of the page:

enter image description here


Solution

  • Things will speed up your Lazy List

    1. If you're in debug mode, that's normal. Don't worry if your app is laggy in debugging. It's completely fine. Just create the APK in release mode (Build -> Generated Signed Bundle/APK), which might solve your problem. This happens because when in debugging, Compose translates bytecode in runtime using JIT. Make sure you're also using the R8 compiler in the release build. This is extremely important to improve general performance, mostly for lambda expressions which are heavily used in compose.

    2. Set a key for your item. Initialize your Lazy list like this.

    LazyColumn() {
        items(
            count = cartItems.size,
            key = {
                cartItems[it].cartItem.id
            },
            itemContent = { index ->
                val cartItemData = cartItems[index]
                CartItemWithActions(data = cartItemData)
                Divider(
                    color = colorResource(id =R.color.separator_line)
                )
            }
        )
    }
    

    Setting a key works similar to the DiffUtil class in RecyclerView. Check the Maciej Przybylski's post.

    1. Make sure every variable is using the remember {} block. That's because you don't want to perform heavy operations every time a recomposition happens, which happens every time you change something on the UI side, like a text change.
    @Composable
    fun MyComposable() {
        val list = listOf(654, 551, 121, 155, ...)
        val wrongList = list.sorted() // <- Don't do this
        val correctList = remember { mutableStateOf(list.sorted()) } // <- Do this
    
        // NB: you should perform operations like this inside your domain layer 
        //  (ex. use cases) or view model, not inside a composable function
        ...
    }
    
    1. You can also use contentType, which defines the type of object in a list. This is useful if you have headers or different types of objects in your list. Learn more here.

    2. Baseline Profiles. If you've tried everything but your list is still missing frames this might be it. In this talk, Rahul Ravikumar (Google engineer) reveals how Baseline Profiles improve performances up to 40%. What's this? Compose is a library and not native XML. This means that every time you execute your app, the code has to be translated at runtime. You can pre-execute and save all this code when installing the app using Baseline Profiles. Check these links: Baseline Profiles, Improving Performance with Baseline Profiles.

    Check these resources never to have performance issues again. I highly suggest watching these videos: Optimizing Render Performance of Jetpack Compose, Performance best practices for Jetpack Compose, and reading this post.

    A Good News

    Generally speaking, Jetpack Compose is not great in terms of performance compared to XML. We now know this issue is due to the way Modifiers have been created. I highly suggest you watch this video. Another key factor is its use of the Skia rendering engine, the same one used by Flutter, instead of the native rendering engine employed by XML. The good news is that the Jetpack Compose team has been working on a new way to improve performance for a few months now. This new way leverages the use of Modifier.Node to avoid executing lots of useless operations under the hood. The best thing about this approach is that you won't be required to change anything in your code, and it will be completely retro-compatible.

    Available from Jetpack Compose 1.5.0

    Original Answer

    SOLVED! Reading this reddit I found out the problem is only in the debug version. It seems crazy but it's true. That's because debug versions of Compose apps have a lot going on under the hood which impacts performance (pretty similar to what happens with Flutter). To solve the problem the only thing you need to do is to create a release version of your app. To do that, go to Build -> Generated Signed Bundle/APK. Create the key and then select release.

    Enjoy your smooth app!