1

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?

progquester
  • 1,228
  • 14
  • 23

1 Answers1

1

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.

chuckj
  • 27,773
  • 7
  • 53
  • 49
  • thank you very much. The code is only used to describe my problem and will not actually be used in the product. What I'm actually trying to figure out is how, internally, the SnapshotStateList is checking out that it's changed and calling callbacks like writeable. – progquester Jul 18 '23 at 06:45
  • The snapshot system uses a multi-version concurrency control algorithm that uses copy-on-write to produce a snapshot local mutable copy of the data before modifying the data. The SnapshotStateList uses an immutable array internally that uses a persistent data structure that allows the copy to be incremental. In other words, it doesn't detect the change. When you change state object a copy is produced. The snapshot maintains a set of objects that have been copied and composition is notified about this set and will recompose any function that read any object in the set. – chuckj Jul 20 '23 at 21:19