The refactors I did to stop my Jetpack Compose LazyColumn from constantly recomposing
Tags: android, kotlin, mobile, tristan
Published: 2024-05-03 17:49:02

### Table of contents
1. [Resources](#resources)
2. [The problem I was facing](#prob)
3. [The data class fix](#data)
4. [Unstable Lambdas](#unstable)
5. [Stabilizing the Lambdas](#stable)

### My app on the Google play store
- [The app](https://play.google.com/store/apps/details?id=elliott.software.clicker)

### Resources <a name="resources"></a>
- [Gotchas in Jetpack Compose Recomposition](https://multithreaded.stitchfix.com/blog/2022/08/05/jetpack-compose-recomposition/)

### TLDR(too long didn't read)
-  read the [Gotchas in Jetpack Compose Recomposition](https://multithreaded.stitchfix.com/blog/2022/08/05/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 <a name="prob"></a>
- I have my real time chat application that connects to Twitch's web server via web-socket. UI demonstration, [HERE](https://twitter.com/AndroidTristan/status/1785842746357399935). 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 <a name="data"></a>
- if you are passing any data classes into a LazyColumn they should always use val over var.
```kotlin
//CORRECT
data class Test(
    val testingOne:Boolean,
    val testingTwo:Boolean
)

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

```
- Adding var will cause compose to view the Test data class as [Unstable](https://developer.android.com/develop/ui/compose/performance/stability) meaning that it will not be able to skip any sort of re-compositions

### Unstable lambdas <a name="unstable"></a>
- 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](https://multithreaded.stitchfix.com/blog/2022/08/05/jetpack-compose-recomposition/) article. Which means given this code:
```kotlin
@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")
            }
        }
    }
}

```
- 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 <a name="stable"></a>
- 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](https://multithreaded.stitchfix.com/blog/2022/08/05/jetpack-compose-recomposition/) article. I have used the remember method:
```kotlin
@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")
            }
        }
    }
}

```

- 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](https://twitter.com/AndroidTristan).