Home > database >  Android LiveData/StateFlow List Item Property Update Issue
Android LiveData/StateFlow List Item Property Update Issue

Time:02-22

So I'm updating my RecylerView with StateFlow<List> like following:

My data class:

data class Student(val name: String, var isSelected: Boolean)

My ViewModel logic:

fun updateStudentsOnSelectionChanged(targetStudent: Student) {
    val targetIndex = _students.value.indexOf(targetStudent)
    val isSelected = !targetStudent.isSelected

    _students.value[targetIndex].isSelected = isSelected        //<- doesn't work
} 

Problem: The UI is not changed, but the isSelected inside _student is changed, what's going on? (same to LiveData)

CodePudding user response:

I assume _students is a StateFlow<List>. Changing the isSelected property of the Student model doesn't trigger the StateFlow. The workaround would be to make the isSelected property of the Student data class immutable to get it compared when new state is set, create a MutableList out of the current list and copy the existing Student object with the new value for isSelected property:

data class Student(val name: String, val isSelected: Boolean)

val students = _students.value.toMutableList()
students[targetIndex] = students[targetIndex].copy(isSelected = isSelected)
_students.value = students

CodePudding user response:

Ok, thanks to @Tenfour04 and @Sergey, I finally found out that StateFlow/LiveData cannot detect the internal changes, that's because they are both actually comparing the Reference of the .value.

That means, If I want to force the StateFow<List> to update, the only way is to assign a new List to it, therefore I created the following helper extension function:

fun <T> List<T>.mapButReplace(targetItem: T, newItem: T) = map {
    if (it == targetItem) {
        newItem
    } else {
        it
    }
}

//this function returns a new List with the content I wanted

In Fragment:

val itemCheckAction: (Student) -> Unit = { student ->
    val newStudent = student.copy(isSelected = !student.isSelected)    //prepare a new Student

    viewModel.updateStudentsOnSelectionChanged(student, newStudent)    //update the StateFlow
}

In ViewModel:

fun updateStudentsOnSelectionChanged(currentStudent: Student, newStudent: Student) {
    val newList = _students.value.mapButReplace(currentStudent, newStudent)

    _students.value = newList       //assign a new List with different reference
}
  • Related