How to update just only one variable of an UiState...
# compose
p
How to update just only one variable of an UiState sealed class?
This one:
Copy code
sealed interface MainScreenUiState {
    object Loading : MainScreenUiState
    data class Error(val throwable: Throwable) : MainScreenUiState
    data class Success(
        val busStopsServerReady: Boolean,
        val busStopsDataAmount: Int,
    ) : MainScreenUiState
}
Which is initialized this way:
Copy code
init {
    viewModelScope.launch(Dispatchers.IO) {
        try{
            // sources status
            val busStopsServerReady = networkBusRepository.getStops().isNotEmpty()
            val busStopsDataAmount = defaultMainBusRepository.getBusStops().size

            // update ui state
            _uiState.value = MainScreenUiState.Success(
                busStopsServerReady = busStopsServerReady,
                busStopsDataAmount = busStopsDataAmount
            )
        } catch (e: Throwable) {
            _uiState.value = MainScreenUiState.Error(e)
        }
    }
}
What happens if I only want to update one of the Sucess variables?
Copy code
fun updateUiState(value: Int) {
    //todo _uiState.busStopsDataAmount = value
}
s
Your sealed interface
MainScreenUiState
is immutable, meaning instances of it cannot be changed after they are created
instead, you issue a new instance with the important properties modified. See the
.copy(...)
method here https://kotlinlang.org/docs/data-classes.html#copying
p
what? I'm using the sealed interface pattern showed in google app architecture samples
they are using this, so it must be possible to change values
s
What line is your
_uiState
defined?
p
Copy code
private val _uiState = MutableStateFlow<MainScreenUiState>(MainScreenUiState.Loading)
val uiState: StateFlow<MainScreenUiState> = _uiState
m
The state objects themselves are Immutable not the
_uiState.value
. So the normal way to update a single property in the state object is to use the
copy
function. But since you have a sealed interface for the state, not every class in the sealed hierarchy has the property you want to update. My preferred approach to this is to add state updating functions in the interface. Basically have a function for each event that can change the state, and then each implementor of the interface, it returns a new state object based on the logic for what should happen for that event when you are in the given state. If the event is not valid for the current state, then that state could make it a no op and return itself, throw an exception, or go to an error state, based on your need
p
it seems to be very complex, I don't understand you... maybe can you give a sample for modifying the value of my sample?
m
I don't know what your rules are. What should
updateUiState
do if the current state is
Loading
. Should it create a
Success
state with a default
busStopsServerReady
?
Copy code
sealed interface MainScreenUiState {
    fun updateState(value: Int): MainScreenUiState
    object Loading : MainScreenUiState {
        fun updateState(value: Int): MainScreenUiState = Success(true, value)
    }
    data class Error(val throwable: Throwable) : MainScreenUiState {
        fun updateState(value: Int): MainScreenUiState = this
    }
    data class Success(
        val busStopsServerReady: Boolean,
        val busStopsDataAmount: Int,
    ) : MainScreenUiState {
       fun updateState(value: Int): MainScreenUiState = copy(busStopDataAmount = value)
    }
}

fun updateUiState(value: Int) {
    //todo _uiState.value = _uiState.value.updateState(value)
}
p
well, I don't feel confortable with that approach
I don't need a function for updating content on error and loading
I just need to update a single value on my success state
does not exist a cleaner and simpler way?
m
Then you need to do an instance of check in the
updateUiState
function, but polymorphism is generally considered better and simpler then doing a type check.
p
can you show me that other way? with the check
m
Copy code
fun updateUiState(value: Int) {
    val currentValue = _uiState.value as? Success ?: return
    ui_State.value = currentValue.copy(busStopDataAmount = value)
}
s
MutableStateFlow
has its own update function for convenience https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-state-flow/#1996476265%2FFu[…]ns%2F1975948010 e.g.
Copy code
class CounterModel {
    private val _counter = MutableStateFlow(0) // private mutable state flow
    val counter = _counter.asStateFlow() // publicly exposed as read-only state flow

    fun inc() {
        _counter.update { count -> count + 1 } // atomic, safe for concurrent use
    }
}
p
Seri can you apply that to my sample?
s
I won't write it for you, no. But I encourage you to think about how
StateFlow
is being used here, and how it's a container for the underlying data
p
I definitively still have a problem with flows understanding
I checked the site but didn't found any .update sample there applyed to a Success sealed class like in my case
can't figure out how to migrate your proposal to my sample
I finally get it working doing this:
Copy code
if (_uiState.value is MainScreenUiState.Success)
    _uiState.value = (_uiState.value as MainScreenUiState.Success).copy(busStopsDataAmount = 1)
I don't like... but seems to be the simpler way, its very similar to the one propossed by Michael I think