我有一个ArrayList
val tasks = remember { mutableStateListOf<TaskItem>() }
任务项在哪里
@Keep
@Parcelize
data class Task(
var task: String,
var done: Boolean = false
) : Parcelable
和
@Keep
@Parcelize
data class TaskItem(var id: String, var task: Task) : Parcelable
当我进入目标屏幕时,将一些数据传递到
tasks
:
LaunchedEffect(key1 = Unit) {
tasks.clear()
tasks.addAll(data.tasks)
Log.wtf("TST_data.tasks", data.tasks.map(TaskItem::task).toString())
// Logs: TST_data.tasks [Task(task=Broccoli 🥦, done=true)]
}
其中,
data
由 DataModel 类驱动:
class DataModel(private val application: Application) : AndroidViewModel(application) {
....
val tasks = mutableStateListOf<TaskItem>()
....
}
到目前为止一切都很好,但是在对
tasks
进行了一些更改之后,假设我将 done
的值更改为 false
并且我想在离开屏幕之前保存它。为了确保有一些变化,我比较了这样的列表:
if(data.tasks.map(TaskItem::task).toList() != tasks.map(TaskItem::task).toList())
// proceed with saving
else
// a toast massage: No changes made.
问题是,它进入了
else
块,这意味着尽管我做了更改,但没有任何更改。在调用 if
语句之前,我尝试记录这些列表以查看元素:
Log.wtf("TST_data.tasks", data.tasks.map(TaskItem::task).toString())
Log.wtf("TST_tasks", tasks.map(TaskItem::task).toString())
结果如下:
TST_data.tasks [Task(task=Broccoli 🥦, done=false)]
TST_tasks [Task(task=Broccoli 🥦, done=false)]
tasks
内的改变值也改变了data.tasks
内的值,这是不可能的,因为我确实注意到改变了data.tasks
内的任何内容。
如您所见,每当我更改
data.tasks
时,tasks
都会发生变化。
这应该是不可能的!为什么会发生这种情况以及如何防止这种变化?
如果将相同的项目(对 DataModel 实例的引用)放入两个不同的列表中,则两个列表都指向同一个实例。如果您通过更改该实例的
var
属性之一来修改该实例,则两个列表都会看到相同的更改,因为它们正在查看同一实例。
因此,如果不执行所谓的深层复制,就无法比较数据的新旧版本。创建新列表时复制列表中的每个单独实例看起来像这样:
list.map { it.copy() }
当然,您必须在更改列表中的项目之前执行此操作。如果模型类中的任何项 also 具有
var
属性,事情就会变得更加复杂。这非常容易出错,这就是为什么建议不要在任何模型类中使用任何 var
属性。它们应该是不可变的类。当您需要更改某些内容时,您可以将列表中的项目替换为应用了更改的副本。这就是为什么 Kotlin 数据类提供了方便的 copy()
函数,允许您在进行复制时更改特定属性值。
除了容易出错之外,它还出于同样的原因破坏了 Compose 的状态功能。状态无法检测来自变异类的更改,因为当它们发生变异时,有关先前状态的信息就会丢失,并且无法进行比较以检测更改。