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