I need to have a state in a ViewModel which gets updated by collecting from flows. my State class:
data class DevicesUIState(
val currentGroup: GroupEntity? = null,
val currentGroupSubGroups: List<GroupEntity> = listOf(),
val currentGroupSubDevices: List<DeviceEntity> = listOf(),
val allDevices: List<DeviceEntity> = listOf(),
)
Each property should be set from a flow. The flow setting currentGroup & allDevices property should not wait for each other to emit a result. The flow setting currentGroupSubGroups & currentGroupSubDevices depends on currentGroup being set.
Each flow collects from a room DB
So far my code to set the state properties looks like this
val groupId: Int?
val uiState: StateFlow<DevicesUIState> = groupsRepository.groupFlow(groupId)
.mapLatest {
Timber.e("Mapping currentGroup")
DevicesUIState(currentGroup = it)
}.flatMapLatest { uiState ->
Timber.e("Mapping currentGroupSubGroups")
groupsRepository.subGroupsFlow(uiState.currentGroup?.id).map {
uiState.copy(currentGroupSubGroups = it)
}
}.flatMapLatest { uiState ->
Timber.e("Mapping currentGroupSubDevices")
liteDeviceRepository.subDevicesFlow(uiState.currentGroup?.id).map {
uiState.copy(currentGroupSubDevices = it)
}
}.flatMapLatest { uiState ->
Timber.e("Mapping allDevices")
liteDeviceRepository.allDevicesFlow().map {
uiState.copy(allDevices = it)
}
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000L),
initialValue = DevicesUIState()
)
It's working. But looking at the logged messaged the flows are collected more offend that i expected. So i have a feeling my code is not the correct way of doing things.
Log
I --> GET https://example.com/api/groups
E Mapping currentGroup
E Mapping currentGroupSubGroups
E Mapping currentGroupSubDevices
E Mapping allDevices
I <-- 200 https://example.com/api/groups (393ms, unknown-length body)
E Mapping currentGroup
E Mapping currentGroupSubGroups
E Mapping currentGroupSubDevices
E Mapping allDevices
I --> GET https://example.com/api/devices
I <-- 200 https://example.com/api/devices (85ms, unknown-length body)
E Mapping allDevices
I --> GET https://example.com/api/extraDevicesData
I <-- 200 https://example.com/api/extraDevicesData (258ms, unknown-length body)
E Mapping allDevices
CodePudding user response:
To build this so the flows that aren't dependent on each other can run in parallel, I'd change it as follows.
The group and the two that are dependent on group can be created using flatMapLatest
with an inner combine
. This allows either the subGroups or the subDevices to change without triggering a restart of other flows.
Then that result and the allDevices
flow can also be combined. Likewise, this allows the group and the allDevices to not trigger each other to restart.
Sorry for any syntax errors. I'm not testing this.
val uiState: StateFlow<DevicesUIState> = groupsRepository.groupFlow(groupId)
.flatMapLatest { currentGroup ->
val id = currentGroup.id
groupsRepository.subGroupsFlow(id)
.combine(liteDeviceRepository.subDevicesFlow(id)) { subGroups, subDevices ->
Timber.e("Combining subGroups and subDevices")
DevicesUIState(
currentGroup = currentGroup,
currentGroupSubGroups = subGroups,
currentGroupSubDevices = subDevices
)
}
}.combine(liteDeviceRepository.allDevicesFlow()) { uiState, allDevices ->
Timber.e("Combining groups and allDevices")
uiState.copy(allDevices = allDevices)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000L),
initialValue = DevicesUIState()
)
You might also consider debouncing the two sub flows before combining them if they both frequently emit new values at the same time.