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

发布于 2025-01-17 04:23:50 字数 1856 浏览 1 评论 0原文

当我使用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")
    }

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

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

时光暖心i 2025-01-24 04:23:50

您每次都会发出相同的对象。 Flow 不关心相等性并发出它 - 您可以尝试手动收集它来检查它,但 Compose 会尝试尽可能地减少重组次数,因此它会检查是否状态值实际上已经改变了。

由于您发出可变列表,因此相同的对象存储在可变状态值中。它无法跟踪该对象的更改,当您再次发出它时,它会进行比较并发现数组对象是相同的,因此不需要重新组合。您可以在

解决方案是将可变列表转换为不可变列表:每次发出时它都会成为一个新对象。

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

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.

items.emit(_items.toImmutableList())

An other option to consider is using 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")
    }
}
哭了丶谁疼 2025-01-24 04:23:50

这是 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.

揪着可爱 2025-01-24 04:23:50

Room 数据库也有同样的问题。数据库中的每个新行也在状态中更新,但实体内部的更改不可见。按照 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 <>函数 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:

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

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 on List<> and SnapshotStateList<> is the same. Composable and StateCollector need no changes too.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文