androidkotlinandroid-jetpack-composekotlin-flowcompose-recomposition

Recommposition is not happening when I update a MutableStateFlow<List<AttendanceState>>


I have a MutableStateFlow<List<AttendanceState>>,

var attendanceStates = MutableStateFlow<List<AttendanceState>>(arrayListOf())
    private set

My AttendanceState data class.

data class AttendanceState (
    var memberId: Int,
    var memberName: String = "",
    var isPresent: Boolean = false,
    var leaveApplied: Boolean = false
)

The list is rendered by a LazyColumn

The LazyColumn contains Checkboxes. If i update the checkbox, the event is propagated to the ViewModel and from there I'm changing the value in the list

attendanceStates.value[event.index].copy(leaveApplied = event.hasApplied)
attendanceStates.value = attendanceStates.value.toList()

But this is not updating the LazyColumn.

Snippet of my implementation:

val attendances by viewModel.attendanceStates.collectAsState()

LazyColumn(modifier = Modifier.fillMaxWidth().padding(top = 24.dp)) {
        Log.e("Attendance","Lazy Column Recomposition")
        items(attendances.size) { index ->
            AttendanceCheckBox(attendanceState = attendances[index], modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), onAttendanceStatusChangeListener = { viewModel.onEvent(AttendanceEvent.IsPresentStatusChanged(index, it)) }, onLeaveAppliedStatusChangeListener = { viewModel.onEvent(AttendanceEvent.IsLeaveAppliedStatusChanged(index, it)) })
        }
    }

Re-composition is not happening.


Solution

  • I would recommend SnapshotStateList instead of a standard List, this will guarantee an update without having to create a new instance of it like what you would do with an ordinary List, assuming you call AttendanceState instance copy() and updating at least one of its properties with a different value.

    var attendanceStates = MutableStateFlow<SnapshotStateList>(mutableStateListOf())
        private set
    

    I would also recommend changing the way you use your LazyColumn where items are mapped by their keys not just by their index position,

    LazyColumn(modifier = Modifier.fillMaxWidth().padding(top = 24.dp)) {
            items(attendances, key = {it.memberId}) {
                   AttendanceCheckBox(...)
            }
    }
    

    and if you still need the index position.

    LazyColumn(modifier = Modifier.fillMaxWidth().padding(top = 24.dp)) {
           itemsIndexed(attendances, key = { index, item ->   
                  item.memberId
           }) { item, index ->
                  AttendanceCheckBox(...)
           }
    }
    

    You should also use update when updating your StateFlow instead of modifying its value directly to make it concurrently safe.

    attendanceStates.update { list ->
          val idx = event.idx
          list[idx] = list[idx].copy(leaveApplied = event.hasApplied)
          list
    }