修改列表时,Jetpack compose collectAsState()未收集热流



当我使用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 {}接收更新的列表,但它不是。SharedFlowStateFlow并不重要,两者的行为相同。我能让它工作的唯一方法是创建一个新的列表并发出它。当我使用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")
}

我不应该创建一个新的列表。知道我做错了什么吗?

每次都发射相同的对象。Flow不关心相等并发出它——您可以尝试手动收集它来检查它,但Compose试图尽可能减少重新计算的次数,因此它会检查状态值是否真的发生了更改。

由于您正在发出一个可变列表,所以相同的对象存储在可变状态值中。它无法跟踪对该对象的更改,当您再次发射它时,它会进行比较并看到数组对象是相同的,因此不需要重新组合。你可以在这一行添加一个断点来查看发生了什么

解决方案是将可变列表转换为不可变列表:它将是每个emit上的一个新对象。

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")
}
}

这是state和jetpack compose的预期行为。只有当状态值发生变化时,Jetpack compose才会重新组合。由于列表操作只更改对象的内容,而不更改对象引用本身,因此不会重新组合。

Room数据库也有同样的问题。数据库中的每一个新行也在State中更新,但实体内部的更改不可见。根据Phil Dukhov的解决方案,我更改了viewModel,这样数据库中的每一次更改都会更新UI:

class MemoViewModel(val noteDao: NoteDao):ViewModel() {
companion object {
private const val TIMEOUT_MILLIS = 5_000L
}
val homeUiState: StateFlow<HomeUiState> =
noteDao.getAllNotes().map { it: List<Note>
HomeUiState(it.toMutableStateList())
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
initialValue = HomeUiState(mutableStateListOf())
)
...
/**
* Ui State for HomeScreen
*/
data class HomeUiState(
val noteList: SnapshotStateList<Note>
)

这里的解决方案实际上是Type: SnapshotStateList <> result of Function mutableStateListOf(),它观察List中的每一个变化。这个解决方案非常好,因为我的DAO仍然返回List<>,并且List<>SnapshotStateList<>上的语法是相同的。Composable和StateCollector也不需要更改。

相关内容

  • 没有找到相关文章

最新更新