修改列表时,Jetpack compose collectAsState() 不收集热流
Jetpack compose collectAsState() is not collecting a hot flow when the list is modified
当我使用 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()
returns true
或 false
.
应该无关紧要
viewModelScope.launch {
delay(3000)
val newList = _items.toMutableList()
newList[2] = DataItem("999", "For testing!!!!")
items.emit(newList)
Log.e("ViewModel", "Updated list")
}
我不应该创建新列表。知道我做错了什么吗?
这是 state 和 jetpack compose 的预期行为。 Jetpack compose 仅在状态值发生变化时才重新组合。由于列表操作仅更改对象的内容,而不更改对象引用本身,因此不会重新组合。
你每次发射同一个物体。 Flow
不关心相等性并发出它 - 您可以尝试手动收集它以检查它,但 Compose 会尝试尽可能减少重组次数,因此它会检查状态值是否具有实际上已经改变了。
并且由于您发出的是可变列表,所以相同的对象存储在可变状态值中。它无法跟踪对该对象的更改,当您再次发出它时,它会比较并发现数组对象是相同的,因此不需要重新组合。您可以在 this line 处添加断点以查看发生了什么。
解决方案是将可变列表转换为不可变列表:每次发射时都会成为一个新对象。
items.emit(_items.toImmutableList())
要考虑的另一个选项是使用 mutableStateListOf
:
private val _items = mutableStateListOf<DataItem>()
val items: List<DataItem> = _items
suspend fun getItems() {
_items.clear()
viewModelScope.launch {
repeat(5) {
_items.add(DataItem(it.toString(), "Title $it"))
}
}
viewModelScope.launch {
delay(3000)
val newItem = DataItem("999", "For testing!!!!")
_items[2] = newItem
Log.e("ViewModel", "Updated list")
}
}
当我使用 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()
returns true
或 false
.
viewModelScope.launch {
delay(3000)
val newList = _items.toMutableList()
newList[2] = DataItem("999", "For testing!!!!")
items.emit(newList)
Log.e("ViewModel", "Updated list")
}
我不应该创建新列表。知道我做错了什么吗?
这是 state 和 jetpack compose 的预期行为。 Jetpack compose 仅在状态值发生变化时才重新组合。由于列表操作仅更改对象的内容,而不更改对象引用本身,因此不会重新组合。
你每次发射同一个物体。 Flow
不关心相等性并发出它 - 您可以尝试手动收集它以检查它,但 Compose 会尝试尽可能减少重组次数,因此它会检查状态值是否具有实际上已经改变了。
并且由于您发出的是可变列表,所以相同的对象存储在可变状态值中。它无法跟踪对该对象的更改,当您再次发出它时,它会比较并发现数组对象是相同的,因此不需要重新组合。您可以在 this line 处添加断点以查看发生了什么。
解决方案是将可变列表转换为不可变列表:每次发射时都会成为一个新对象。
items.emit(_items.toImmutableList())
要考虑的另一个选项是使用 mutableStateListOf
:
private val _items = mutableStateListOf<DataItem>()
val items: List<DataItem> = _items
suspend fun getItems() {
_items.clear()
viewModelScope.launch {
repeat(5) {
_items.add(DataItem(it.toString(), "Title $it"))
}
}
viewModelScope.launch {
delay(3000)
val newItem = DataItem("999", "For testing!!!!")
_items[2] = newItem
Log.e("ViewModel", "Updated list")
}
}