在协程范围内重新分配 mutableStateList 后无法触发组合重新组合
Unable to trigger compose re-composition after re-assigning mutableStateList inside a coroutine scope
我对 Jetpack compose 和了解重新组合的工作原理仍然有点陌生。
所以我在 ViewModel
.
下面有一段代码调用
SnapshotStateList
var mutableStateTodoList = mutableStateListOf<TodoModel>()
private set
在构建视图模型期间,我执行了一个房间数据库调用
init {
viewModelScope.launch {
fetchTodoUseCase.execute()
.collect { listTypeTodo ->
mutableStateTodoList = listTypeTodo.toMutableStateList()
}
}
}
然后我有一个来自 ui 的动作,它会触发向列表添加一个新的待办事项,并期待 ui 的重新组合显示一张可组合的卡片
fun onFabClick() {
todoList.add(TodoModel())
}
我不明白为什么它不触发重新合成。
但是,如果我修改下面的初始化代码块,并调用 onFabClick()
操作,它会触发重新组合
init {
viewModelScope.launch {
fetchTodoUseCase.execute()
.collect { listTypeTodo ->
mutableStateTodoList.addAll(listTypeTodo)
}
}
}
或者这样,在协程作用域之外取出 mutableStateList
的重新分配也有效(触发重新组合)。
init {
// just trying to test a re-assigning of the mutableStateList property
mutableStateTodoList = emptyList<TodoModel>().toMutableStateList()
}
不确定ui问题出在协程上下文或SnapshotStateList
本身。
当代码以下面的方式实现时,一切也都按预期工作,在包装器内使用标准列表并执行复制(创建新引用)并在包装器内重新分配列表。
var todoStateWrapper by mutableStateOf<TodoStateWrapper>(TodoStateWrapper)
private set
相同的初始化{...}调用
init {
viewModelScope.launch {
fetchTodoUseCase.execute()
.collect { listTypeTodo ->
todoStateWrapper = todoStateWrapper.copy (
todoList = listTypeTodo
)
}
}
}
总而言之,在协程范围内,为什么这样行得通
// mutableStateList
todoList.addAll(it)
而这个没有?
// mutableStateList
todoList = it.toMutableStateList()
为什么普通列表在包装器中并执行 copy()
工作?
Compose 中的可变状态只能跟踪包含值的更新。下面是关于如何实现 MutableState
的简化代码:
class MutableState<T>(initial: T) {
private var _value: T = initial
private var listeners = MutableList<Listener>
var value: T
get() = _value
set(value) {
if (value != _value) {
_value = value
listeners.forEach {
it.triggerRecomposition()
}
}
}
fun addListener(listener: Listener) {
listeners.add(listener)
}
}
当某个视图使用该状态时,该视图会订阅该特定状态的更新。
因此,如果您声明 属性 如下:
var state = MutableState(1)
并尝试用 state = 2.toMutableState()
更新它(这类似于您的 mutableStateTodoList = listTypeTodo.toMutableStateList()
),无法调用 triggerRecomposition
,因为您创建了一个重置所有侦听器的新对象。相反,要触发重组,您应该使用 state.value = 2
.
更新它
和mutableStateList
一样,更新值类比是MutableList
接口更新包含列表的任意方法,包括addAll
.
在 init
内部它可以工作,因为到目前为止没有视图订阅此状态,并且这是唯一应该使用 toMutableStateList
等方法的地方。
重要的是始终将可变状态定义为不可变 属性 和 val
以防止此类错误。要使其仅在视图模型中可变,您可以这样定义它,并在 _mutableStateTodoList
:
上进行更新
private val _mutableStateTodoList = mutableStateListOf<TodoModel>()
val mutableStateTodoList: List<TodoModel> = _mutableStateTodoList
您可以使用 var
的唯一例外是使用 mutableStateOf
和委托 - 这是您可以将它与 private set
一起使用的地方,因为在这种情况下委托会为你不修改容器,而只是它 value
属性。这种方法不能应用于 mutableStateListOf
,因为在列表的情况下没有单个 value
字段负责数据。
var someValue by mutableStateOf(1)
private set
我对 Jetpack compose 和了解重新组合的工作原理仍然有点陌生。
所以我在 ViewModel
.
SnapshotStateList
var mutableStateTodoList = mutableStateListOf<TodoModel>()
private set
在构建视图模型期间,我执行了一个房间数据库调用
init {
viewModelScope.launch {
fetchTodoUseCase.execute()
.collect { listTypeTodo ->
mutableStateTodoList = listTypeTodo.toMutableStateList()
}
}
}
然后我有一个来自 ui 的动作,它会触发向列表添加一个新的待办事项,并期待 ui 的重新组合显示一张可组合的卡片
fun onFabClick() {
todoList.add(TodoModel())
}
我不明白为什么它不触发重新合成。
但是,如果我修改下面的初始化代码块,并调用 onFabClick()
操作,它会触发重新组合
init {
viewModelScope.launch {
fetchTodoUseCase.execute()
.collect { listTypeTodo ->
mutableStateTodoList.addAll(listTypeTodo)
}
}
}
或者这样,在协程作用域之外取出 mutableStateList
的重新分配也有效(触发重新组合)。
init {
// just trying to test a re-assigning of the mutableStateList property
mutableStateTodoList = emptyList<TodoModel>().toMutableStateList()
}
不确定ui问题出在协程上下文或SnapshotStateList
本身。
当代码以下面的方式实现时,一切也都按预期工作,在包装器内使用标准列表并执行复制(创建新引用)并在包装器内重新分配列表。
var todoStateWrapper by mutableStateOf<TodoStateWrapper>(TodoStateWrapper)
private set
相同的初始化{...}调用
init {
viewModelScope.launch {
fetchTodoUseCase.execute()
.collect { listTypeTodo ->
todoStateWrapper = todoStateWrapper.copy (
todoList = listTypeTodo
)
}
}
}
总而言之,在协程范围内,为什么这样行得通
// mutableStateList
todoList.addAll(it)
而这个没有?
// mutableStateList
todoList = it.toMutableStateList()
为什么普通列表在包装器中并执行 copy()
工作?
Compose 中的可变状态只能跟踪包含值的更新。下面是关于如何实现 MutableState
的简化代码:
class MutableState<T>(initial: T) {
private var _value: T = initial
private var listeners = MutableList<Listener>
var value: T
get() = _value
set(value) {
if (value != _value) {
_value = value
listeners.forEach {
it.triggerRecomposition()
}
}
}
fun addListener(listener: Listener) {
listeners.add(listener)
}
}
当某个视图使用该状态时,该视图会订阅该特定状态的更新。
因此,如果您声明 属性 如下:
var state = MutableState(1)
并尝试用 state = 2.toMutableState()
更新它(这类似于您的 mutableStateTodoList = listTypeTodo.toMutableStateList()
),无法调用 triggerRecomposition
,因为您创建了一个重置所有侦听器的新对象。相反,要触发重组,您应该使用 state.value = 2
.
和mutableStateList
一样,更新值类比是MutableList
接口更新包含列表的任意方法,包括addAll
.
在 init
内部它可以工作,因为到目前为止没有视图订阅此状态,并且这是唯一应该使用 toMutableStateList
等方法的地方。
重要的是始终将可变状态定义为不可变 属性 和 val
以防止此类错误。要使其仅在视图模型中可变,您可以这样定义它,并在 _mutableStateTodoList
:
private val _mutableStateTodoList = mutableStateListOf<TodoModel>()
val mutableStateTodoList: List<TodoModel> = _mutableStateTodoList
您可以使用 var
的唯一例外是使用 mutableStateOf
和委托 - 这是您可以将它与 private set
一起使用的地方,因为在这种情况下委托会为你不修改容器,而只是它 value
属性。这种方法不能应用于 mutableStateListOf
,因为在列表的情况下没有单个 value
字段负责数据。
var someValue by mutableStateOf(1)
private set