修改列表时,Jetpack composecollectAsState() 未收集热流
当我使用collectAsState()
时,collect {}
仅在传递新列表时触发,而不是在修改和发出时触发。
查看模型
@HiltViewModel
class MyViewModel @Inject constructor() : ViewModel() {
val items = MutableSharedFlow<List<DataItem>>()
private val _items = mutableListOf<DataItem>()
suspend fun getItems() {
_items.clear()
viewModelScope.launch {
repeat(5) {
_items.add(DataItem(it.toString(), "Title $it"))
items.emit(_items)
}
}
viewModelScope.launch {
delay(3000)
val newItem = DataItem("999", "For testing!!!!")
_items[2] = newItem
items.emit(_items)
Log.e("ViewModel", "Updated list")
}
}
}
data class DataItem(val id: String, val title: String)
可组合项
@Composable
fun TestScreen(myViewModel: MyViewModel) {
val myItems by myViewModel.items.collectAsState(listOf())
LaunchedEffect(key1 = true) {
myViewModel.getItems()
}
LazyColumn(
modifier = Modifier.padding(vertical = 20.dp, horizontal = 10.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(myItems) { myItem ->
Log.e("TestScreen", "Got $myItem") // <-- won't show updated list with "999"
}
}
}
我希望 collect {}
接收更新后的列表,但事实并非如此。 SharedFlow
或 StateFlow
并不重要,两者的行为相同。我可以让它工作的唯一方法是创建一个新列表并发出它。当我使用 SharedFlow 时,equals() 返回 true 或 false 并不重要。
viewModelScope.launch {
delay(3000)
val newList = _items.toMutableList()
newList[2] = DataItem("999", "For testing!!!!")
items.emit(newList)
Log.e("ViewModel", "Updated list")
}
我不应该创建一个新列表。知道我做错了什么吗?
When I use collectAsState()
, the collect {}
is triggered only when a new list is passed, not when it is modified and emitted.
View Model
@HiltViewModel
class MyViewModel @Inject constructor() : ViewModel() {
val items = MutableSharedFlow<List<DataItem>>()
private val _items = mutableListOf<DataItem>()
suspend fun getItems() {
_items.clear()
viewModelScope.launch {
repeat(5) {
_items.add(DataItem(it.toString(), "Title $it"))
items.emit(_items)
}
}
viewModelScope.launch {
delay(3000)
val newItem = DataItem("999", "For testing!!!!")
_items[2] = newItem
items.emit(_items)
Log.e("ViewModel", "Updated list")
}
}
}
data class DataItem(val id: String, val title: String)
Composable
@Composable
fun TestScreen(myViewModel: MyViewModel) {
val myItems by myViewModel.items.collectAsState(listOf())
LaunchedEffect(key1 = true) {
myViewModel.getItems()
}
LazyColumn(
modifier = Modifier.padding(vertical = 20.dp, horizontal = 10.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(myItems) { myItem ->
Log.e("TestScreen", "Got $myItem") // <-- won't show updated list with "999"
}
}
}
I want the collect {}
to receive the updated list but it is not. SharedFlow
or StateFlow
does not matter, both behave the same. The only way I can make it work is by creating a new list and emit that. When I use SharedFlow
it should not matter whether equals()
returns true
or false
.
viewModelScope.launch {
delay(3000)
val newList = _items.toMutableList()
newList[2] = DataItem("999", "For testing!!!!")
items.emit(newList)
Log.e("ViewModel", "Updated list")
}
I should not have to create a new list. Any idea what I am doing wrong?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您每次都会发出相同的对象。 Flow 不关心相等性并发出它 - 您可以尝试手动收集它来检查它,但 Compose 会尝试尽可能地减少重组次数,因此它会检查是否状态值实际上已经改变了。
由于您发出可变列表,因此相同的对象存储在可变状态值中。它无法跟踪该对象的更改,当您再次发出它时,它会进行比较并发现数组对象是相同的,因此不需要重新组合。您可以在
解决方案是将可变列表转换为不可变列表:每次发出时它都会成为一个新对象。
另一个需要考虑的选项是使用 mutableStateListOf:
You emit the same object every time.
Flow
doesn't care about equality and emits it - you can try to collect it manually to check it, but Compose tries to reduce the number of recompositions as much as possible, so it checks to see if the state value has actually been changed.And since you're emitting a mutable list, the same object is stored in the mutable state value. It can't keep track of changes to that object, and when you emit it again, it compares and sees that the array object is the same, so no recomposition is needed. You can add a breakpoint at this line to see what's going on.
The solution is to convert your mutable list to an immutable one: it's gonna be a new object each on each emit.
An other option to consider is using
mutableStateListOf
:这是 state 和 jetpack 组合的预期行为。 Jetpack compose 仅在状态值发生变化时才重新组合。由于列表操作仅更改对象的内容,而不更改对象引用本身,因此组合不会被重新组合。
This is the expected behavior of state and jetpack compose. Jetpack compose only recomposes if the value of the state changes. Since a list operation changes only the contents of the object, but not the object reference itself, the composition will not be recomposed.
Room 数据库也有同样的问题。数据库中的每个新行也在状态中更新,但实体内部的更改不可见。按照 Phil Dukhov 的解决方案,我更改了 viewModel,以便数据库中的每次更改都会更新 UI:
这里的解决方案实际上是
Type: SnapshotStateList <>函数 mutableStateListOf()
的结果,观察列表中的每个变化。该解决方案非常好,因为我的 DAO 仍然返回
List<>
并且List<>
和SnapshotStateList<>
上的语法是相同的。 Composable 和 StateCollector 也不需要更改。Had the same problem with the Room database. Every new row in Database was updated in State too, but changes inside the Entity are not visible. Following Phil Dukhov's solution I've changed my viewModel so that every change in the database updates the UI:
The solution here is actually the
Type: SnapshotStateList <> result of Function mutableStateListOf()
that observes every change in the List.That solution is very nice because my DAO still returns
List<>
and the syntax onList<>
andSnapshotStateList<>
is the same. Composable and StateCollector need no changes too.