I have a typical Android MVVM project (Compose -> ViewModel -> Repository -> RoomDB) and have simplified concept in demonstration below.
What am I trying to achieve?: I want to read two flows from a Room Dao (@Query(...) fun getAll(): Flow<List>) from my repository. They should be combined into a single flow and then returned to my viewModel. From the viewModel I want to transform the flow into a StateFlow that can be observing for as long as the viewModel lives. In the example below I propagate this StateFlow value to a mutable state "item" just to be able to display value in UI for demonstration.
The problem: The combine method on the flow doesn't trigger any changes back up in the hierarchy, neither does collectLatest on a single Dao flow when I try debugging. The funny thing is that collect does...
Now onto the demonstration:
class ViewModel(private val coroutineScope: CoroutineScope) {
private val repository = Repository()
val item = mutableStateOf<String?>(null)
init {
coroutineScope.launch {
repository.item.stateIn(coroutineScope).collect {
item.value = it
}
}
}
fun click() {
coroutineScope.launch {
repository.increaseItem()
}
}
}
class Repository {
private val dao = Dao()
val item: Flow<String> = flow {
// This does NOT trigger
dao.dbItem.combine(dao.dbItem2) { value1, value2 ->
emit("$value1|$value2")
}
// This does NOT trigger
// dao.dbItem.collectLatest {
// emit(it)
// }
// This does trigger...
// dao.dbItem.collect {
// emit(it)
// }
}
suspend fun increaseItem() = dao.increaseDbItem()
}
class Dao {
val dbItem = MutableStateFlow("0")
val dbItem2 = MutableStateFlow("A")
suspend fun increaseDbItem() {
dbItem.emit("${dbItem.value} 0")
dbItem2.emit("${dbItem2.value} A")
}
}
@Composable
fun AtteTest() {
val coroutineScope = rememberCoroutineScope()
val viewModel = ViewModel(coroutineScope)
TestAppTheme {
Surface {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Text("Value:")
Text(viewModel.item.value ?: "")
}
Button(onClick = {
viewModel.click()
}) {
Text(text = "click")
}
}
}
}
}
}
@Preview
@Composable
fun PreviewAtteTest() {
AtteTest()
}
Works after the following changes:
Return the combine instead of a new flow containing the combine
The issue with collectLatest was solved by changing from a flow to a callbackFlow: