I have this stateflow val in my viewmodel, it's in...
# compose
p
I have this stateflow val in my viewmodel, it's initialized to the values of a Room database, so it is automatically filled with values.
Copy code
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)
I added a message dialog that displays the number of items obtained from the database, and it is displayed when the
dialogText
message is different from null. The
dialogText
is on my Success uistate as follows:
Copy code
sealed interface BusStopsDBScreenUiState {
    object Loading : BusStopsDBScreenUiState
    data class Error(val throwable: Throwable) : BusStopsDBScreenUiState
    data class Success(
        val data: List<BusStop>,
        val dialogText: StringResource?
    ) : BusStopsDBScreenUiState
}
The problem now is that I need to set null that
dialogText
variable, to hide the dialog, so I need a method for that on the viewmodel:
Copy code
fun closeDialog() {
    //TODO set dialogText to null
}
but... I don't understand how can I change the dialogText to null without breaking my stateflow val. Some people here tell me that I need to use mutablestateflow, but I don't know how to change my code to achieve it. Please can someone explain me?
k
You could have a separate field:
Copy code
private val dialogText: MutableStateFlow<StringResource>? = MutableStateFlow(null)
and then
Copy code
val uiState: StateFlow<BusStopsDBScreenUiState> = combine(dialogText, 
busDataRepository.getBusStops()) {
    (...)
}
and then
Copy code
fun closeDialog() {
    dialogText.update { null }
}
p
why should I have a separate field? why not in the uistate to follow the uistate pattern? also.. how can that variable become true automatically when the room database results are returned?
there is not a simple way to change the value of my sample? before stateflows this was super easy, how can this be so much complex?
I'm really very frustated with stateflows, sorry
k
The state of your UI should always be a combination of the data + UI actions transformation. Instead of:
Copy code
private val dialogText: MutableStateFlow<StringResource>? = MutableStateFlow(null)
you could have:
Copy code
private val uiEvent: Channel<UiEvent>
and then:
Copy code
fun closeDialog() {
    uiEvent.tryEmit(DialogCloseRequested)
}
or something like this. I think Ballast’s documentation has a good explanation of the different presentation layer patterns: https://copper-leaf.github.io/ballast/wiki/usage/mental-model/#mvi
c
Also https://developer.android.com/topic/architecture/ui-layer/state-production#one-shot-apis Has an example of an internal mutable state flow that gets updates via a function in the viemodel.
p
I'm trying to understand how to do it
The simplest thing whould be to migrate this to a mutablestateflow and be able to update just that message value:
Copy code
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)
but I can't change
val uiState: StateFlow
to
val uiState: MutableStateFlow
and initialize it in the same way, it doesn't compile
Copy code
Type mismatch. Required:MutableStateFlow<BusStopsDBScreenUiState> Found:StateFlow<BusStopsDBScreenUiState>
and I don't understand how can I change it