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?
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)
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.
Use a single triggering mechanism to update your composable. Your list should just be available as a normal variable. You can add/remove/delete items in the list and even update properties of each item. Once you make these updates to the list, you can recompose your composable by generating a random number that is bound to a mutable state observable that is observed in your composable:
Kotlin:
class MyViewModel: ViewModel() {
var childTravellersList = mutableListOf<TravellersDetails>()
var onUpdate = mutableStateOf(0)
private fun updateUI() {
onUpdate.value = (0..1_000_000).random()
}
fun update(index){
childTravellersList[index].error = true
updateUI()
}
}
@Composable
fun MyComposableHandler() {
// This will detect any changes to data and recompose your composable.
viewmodel.onUpdate.value
MyComposable(
travelersList = viewmodel.childTravellersList
)
}
@Composable
fun MyComposable(
travelersList: List<TravellersDetails>
) {
}
You should avoid creating multiple mutable state variables in your viewmodel for different variables that need updating. There is absolutely no need to do this. Just create a single mutable state variable, as shown above. Whenever you need to recompose your composable, you just updateUI function. Logic in your viewmodel should decide what needs to be updated but leave the actual updating mechanism to a single mutable state observable. This is a clean pattern and will make updating your composables much easier.
However, do keep in mind that your UI is normally going to be made up of many composables in a hierarchy. You don't want to recompose the entire hierarchy when just one element changes. For that reason, a mutable state observable should be used for each composable that needs to be recomposed independently of the others in the heirarchy.
The other benefit of using the solution shown above is that you can easily update objects without the need to create new objects. If you want your composable to recompose when only a certain property of the object changes, you cannot use a mutable state observable because they only detect changes to the object themselves and NOT to the object's properties. This is why you are better off to use the triggering method shown above and simply retrieve the updated object when the composable recomposes.