3

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.

z.g.y
  • 5,512
  • 4
  • 10
  • 36
Aswin .A.S
  • 177
  • 1
  • 14
  • 1
    From Compose's standpoint, the list is not changing. Some object inside of that list is changing, and nothing can observe that. Replace your `var` properties in `AttendanceState` with `val`. Emit a new immutable list that contains the new data (old `AttendanceState` objects except for one replaced with a new `AttendanceState` with the mutated data). – CommonsWare Oct 24 '22 at 19:32

2 Answers2

1

Try this:

viewModelScope.launch {
    val helper = ArrayList(attendanceStates.value)
    helper[event.index] = helper[event.index].copy(leaveApplied = event.hasApplied)
    attendanceStates.emit(helper)
}

Changing an item's properties will not trigger a StateFlow, you have to replace the whole item with the changed item and emit a new list.

adwardwo1f
  • 817
  • 6
  • 18
0

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
}
z.g.y
  • 5,512
  • 4
  • 10
  • 36