Aswin .A.S
Aswin .A.S

Reputation: 317

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.

Upvotes: 4

Views: 1298

Answers (2)

z.g.y
z.g.y

Reputation: 6277

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
}

Upvotes: 3

sovanrothaa
sovanrothaa

Reputation: 1042

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.

Upvotes: 1

Related Questions