DEV Community

Cover image for The refactors I did to stop my Jetpack Compose LazyColumn from constantly recomposing
Tristan Elliott
Tristan Elliott

Posted on

The refactors I did to stop my Jetpack Compose LazyColumn from constantly recomposing

Table of contents

  1. Resources
  2. The problem I was facing
  3. The data class fix
  4. Unstable Lambdas
  5. Stabilizing the Lambdas

My app on the Google play store

Resources

TLDR(too long didn't read)

  • read the Gotchas in Jetpack Compose Recomposition article for a deeper understanding
  • don't use var for any of your data classes
  • unstable lambdas are lambdas that contain methods from a View Model or are suspending
  • use remember(){} to stabilize lambdas that are used inside of the LazyColumn items.

The problem I was facing

  • I have my real time chat application that connects to Twitch's web server via web-socket. UI demonstration, HERE. The problem I was facing was that the entire LazyColumn would recompose every time a new chat message was added to the UI. Which as you can imagine causes a lot of unnecessary re-compositions when 50,000 users are all chatting at the same time.

  • I ran into 2 main problems that were causing recompositions, 1)unstable Data class, 2) unstable functions

The data class fix

  • if you are passing any data classes into a LazyColumn they should always use val over var.
//CORRECT
data class Test(
    val testingOne:Boolean,
    val testingTwo:Boolean
)

//WRONG
data class Test(
    var testingOne:Boolean,
    var testingTwo:Boolean
)

Enter fullscreen mode Exit fullscreen mode
  • Adding var will cause compose to view the Test data class as Unstable meaning that it will not be able to skip any sort of re-compositions

Unstable lambdas

  • As it turns out, methods that come from a viewModel or are suspending are also considered unstable. You can read more about this in the Gotchas in Jetpack Compose Recomposition article. Which means given this code:
@Composable
fun TestingOne(streamViewModel: StreamViewModel){
    val listOfString = mutableListOf("one","two","three")
    TestingLazyColumn(
        functionFromViewMode={streamViewModel.updateMethod()},
        stringList =listOfString
    )

}
@Composable
fun TestingLazyColumn(
    functionFromViewMode:() ->Unit,
    stringList:List<String>
){
    LazyColumn(){
        items(stringList){
            Button(onClick ={functionFromViewMode()}) {
                Text("Click me")
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Any time the listOfString is updated, every single one of the items in the string list will be recomposed. If you have 100,000+ items, that is going to cause some jank.

Stabilizing unstable lambdas

  • There are a couple of ways that we can stabalize these lambdas, again, you can read about all the ways inside of the Gotchas in Jetpack Compose Recomposition article. I have used the remember method:
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun TestingOne(streamViewModel: StreamViewModel,bottomModalState: ModalBottomSheetState){

    val listOfString = mutableListOf("one","two","three")
    val updateMethod:()->Unit =remember(streamViewModel) { {
        streamViewModel.updateMethod()
    } }
    //example of stabilizing suspending function:
    val scope = rememberCoroutineScope()
    val showClickedUserBottomModal:()->Unit =remember(bottomModalState) { {
        scope.launch {
            bottomModalState.show()
        }
    } }
        //example of method with multiple parameters:
    val updateClickedUser:(String,String,Boolean,Boolean)->Unit = remember(streamViewModel) { { username, userId, banned, isMod ->
        streamViewModel.updateClickedChat(
            username,
            userId,
            banned,
            isMod
        )
    } }


    TestingLazyColumn(
        functionFromViewMode={updateMethod()},
        stringList =listOfString
    )

}
@Composable
fun TestingLazyColumn(
    functionFromViewMode:() ->Unit,
    stringList:List<String>
){
    LazyColumn(){
        items(stringList){
            Button(onClick ={functionFromViewMode()}) {
                Text("Click me")
            }
        }
    }
}

Enter fullscreen mode Exit fullscreen mode
  • Long story short, remember() allows the compose compiler to skip the recomposition

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.

Top comments (0)