Android Jetpack Compose mutableStateListOf not doing Recomposition

So, I have a mutableStateListOf in viewModel:

var childTravellersList = mutableStateListOf<TravellersDetails>()

TravellersDetails is a data class having a field called error.

this childTravellersList is used in the UI as:

val list = remember{viewModel.childTravellersList}

LazyColumn(state = lazyColumnState) {
    itemsIndexed(list) { index, item ->
        SomeBox(show = if(item.error) true else false)
    }
  }

I have wrote a function in viewModel that updates error of TravellersDetails at given index of childTravellersList as:

fun update(index){
    childTravellersList[index].error = true
}

So, whenever I call this function, the list should get updated.

This updates the list, but UI recomposition is not triggered 😕. Where am I doing wrong?


Solution 1:

mutableStateListOf can only notify about adding/removing/replacing some element in the list. When you change any class inside the list, the mutable state cannot know about it.

Data classes are very good for storing immutable state in unidirectional data flow, because you can always "change" it with copy, while you see the need to pass the new data to view or mutable state. So avoid using var variables with data classes, always declare them as val to prevent such errors.

var childTravellersList = mutableStateListOf<TravellersDetails>()

fun update(index){
    childTravellersList[index] = childTravellersList[index].copy(error = true)
}

An other problem is that you're using val list = remember{viewModel.childTravellersList}: it saves the first list value and prevents updates in future. With ViewModel you can use it directly itemsIndexed(viewModel.childTravellersList)

Solution 2:

Recomposition will happen only when you change the list itself. You can do it like this.

var childTravellersList by mutableStateOf(emptyList<TravellersDetails>())

fun update(indexToUpdate: Int) {
    childTravellersList = childTravellersList.mapIndexed { index, details ->
        if(indexToUpdate == index) details.copy(error = true)
        else details
    }
}

Also, you need not remember this list in you composable as you have done here val list = remember{viewModel.childTravellersList}. Since the MutableState is inside view model it will always survive all recompositions. Just use the viewModel.childTravellersList inside LazyColumn.