Having this on my viewmodel:
val uiState: StateFlow<BusStopsDBScreenUiState> = busDataRepository.getBusStops()
.map<List<BusStop>, BusStopsDBScreenUiState> { busStops ->
BusStopsDBScreenUiState.Success(busStops, Res.string.bus_stops_db_filled)
}
.catch { throwable ->
emit(BusStopsDBScreenUiState.Error(throwable))
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), BusStopsDBScreenUiState.Loading)
fun updateBusStops() {
viewModelScope.launch(Dispatchers.Main) {
launch(Dispatchers.IO) {
busDataRepository.updateBusStops()
}
}
}
With this UiState:
sealed interface BusStopsDBScreenUiState {
object Loading : BusStopsDBScreenUiState
data class Error(val throwable: Throwable) : BusStopsDBScreenUiState
data class Success(
val data: List<BusStop>,
val dialogText: StringResource?
) : BusStopsDBScreenUiState
}
I need to implement a method on my viewmodel that modifies the dialogText
variable from
my Sucess uistate to null, for hidding the dialog when the user press the dialog close button:
fun closeDialog() {
//TODO
}
How can I modify my val uiState: StateFlow<BusStopsDBScreenUiState>
in that new function to set the success dialogText
variable to null without lossing the data
variable content?
1
That can be done by introducing an additional MutableStateFlow:
private val showDialog = MutableStateFlow(true)
Now your uiState needs to be derived from two flows. You can combine them like this:
val uiState: StateFlow<BusStopsDBScreenUiState> = combine(
showDialog,
busDataRepository.getBusStops(),
) { showDialog, busStops ->
BusStopsDBScreenUiState.Success(
data = busStops,
dialogText = if (showDialog) Res.string.bus_stops_db_filled else null,
) as BusStopsDBScreenUiState
}
.catch { throwable ->
emit(BusStopsDBScreenUiState.Error(throwable))
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = BusStopsDBScreenUiState.Loading,
)
Instead of map
this uses the combine’s transform block where you now have the values of both flows available. This allows you to set dialogText
accordingly.
You can now simply toggle the MutableStateFlow’s value to hide the dialog:
fun closeDialog() {
showDialog.value = false
}
When this is called, only the combine’s transform block will be executed again (and everything after that), the busDataRepository.getBusStops()
flow didn’t change so its old value is used. You now have a new BusStopsDBScreenUiState.Success
value in your StateFlow with the same data
but dialogText
set to null.
You might want to consider extracting the mapping logic into a separate function to keep the uiState
property clean of business logic:
private fun BusStopsDBScreenUiState(
showDialog: Boolean,
busStops: List<BusStop>,
): BusStopsDBScreenUiState = BusStopsDBScreenUiState.Success(
data = busStops,
dialogText = if (showDialog) Res.string.bus_stops_db_filled else null,
)
Then you can even convert the function call in the lambda to a reference, which makes it even more concise:
val uiState: StateFlow<BusStopsDBScreenUiState> =
combine(
showDialog,
busDataRepository.getBusStops(),
::BusStopsDBScreenUiState,
).catch { throwable ->
emit(BusStopsDBScreenUiState.Error(throwable))
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = BusStopsDBScreenUiState.Loading,
)
4