kotlinandroid-jetpack-composemutablestateof

How SnapshotStateList detects that a change has occurred?


Suppose I have a SnapshotStateList for Student, and the definition of Student is:

data class Student<val id: Int, var name: String>

val students = mutableStateListOf(Student(0, "Aaron"))

My Jetpack compose in wants to recompose when students changes.

Found the following function to trigger it:

fun addStudent(name: String) {
    students.add(Student(students.size, "Bob"))
}
fun removeStudent(key: Int) {
    students.remove(key)
}
fun replaceStudent(key: Int, name: String) {
    val old = students[key]
    students[key] = Student(old.key, name)
}

But the following function cannot trigger it:

fun modifyStudent(key: Int, name: String) {
    students[key].name = name
}

Why, how does SnapshotStateList detect that a change has occurred?


Solution

  • The snapshot system will detect changes in SnapshotStateList itself, not in changes to mutable state within it.

    The code,

    fun replaceStudent(key: Int, name: String) {
        val old = students[key]
        students[key] = Student(old.key, name)
    }
    

    modifies students and is detected as a change.

    fun modifyStudent(key: Int, name: String) {
        students[key].name = name
    }
    

    modifies the name property of some Student object which is not seen by the snapshot system as a change.

    I recommend you make Student immutable instead of mutable,

    data class Student(val id: Int, val name: String)
    

    Then replaceStudent would be required to modify update the student.

    You could, alternately, change Student to,

    class Student(val id: Int, name: String) {
        var name by mutableStateOf(name);
    }
    

    which will make Student observable and notify Compose whenever the name property is changed.

    I recommend making Student immutable.

    As a bonus, I recommend you use SnapshotStateMap instead of SnapshotStateList in this case as if you ever call removeStudent above the key of the students after the remove will not match the index into the students. Using a SnapshotStateMap will fix this. You also need to change the addStudent to not use size as the next id but, rather, use a global integer or an global atomic (if you are multi-threaded) as, now, creating a new student will overwrite an existing student's data as it causes duplicate key values to be generated if any student was deleted.