When having a sealed class hierarchy for your `uiS...
# compose
l
When having a sealed class hierarchy for your `uiState`:
Copy code
sealed class MyScreenState
data object EmptyState : MyScreenState()
data class ContentState(val title: String, val content: String) : MyScreenState()
How do you handle conditional
uiState
updates based on the current subclass?
Copy code
val uiState: MutableStateFlow<MyScreenState> = MutableStateFlow(EmptyState)

fun onContentChanged(newContent: String) {
    uiState.update { (it as? ContentState)?.copy(content = newContent) ?: it }
}
This seems neither elegant nor readable.
Alternative could be with
when
, but it still requires the lone
it
inside of
update()
Copy code
uiState.update {
    when (it) {
        is ContentState -> it.copy(content = newContent)
        else -> it
    }
}
On the other hand, wrapping the whole
update
block in an
if
or
?.let
is not thread safe. There could be a race condition, where the
uiState
gets updated by another thread just after executing the
if
or
?.let
and we overwrite this other update.
Copy code
(uiState.value as? ContentState)?.let { currentUiState ->
    // potential race condition and overwriting parallel updates
    uiState.value = currentUiState.copy(content = newContent)
}
e
you could
Copy code
run {
    uiState.update {
        if (it !is ContentState) return@run
        it.copy(content = newContent)
    }
}
which could be slightly more efficient since it avoids a CAS in the
!is
case
l
Yes, that's a good suggestion
I tried modifying the "official" `MutableStateFlow.update()`:
Copy code
public inline fun <T, reified U : T> MutableStateFlow<T>.updateIfSubtype(function: (U) -> T) {
    while (true) {
        val prevValue = value
        if (prevValue is U) {
            val nextValue = function(prevValue)
            if (compareAndSet(prevValue, nextValue)) {
                return
            }
        } else return
    }
}
Usage:
Copy code
uiState.updateIfSubtype<MyScreenState, ContentState> { it.copy(content = newContent) }
The only disadvantage is that you have to explicitly specify the parent type.
e
uiState.updateIfSubtype<_, ContentState> { ... }
or swap the parameters around with
Copy code
inline fun <reified U, T> MutableStateFlow<T>.updateIfSubtype(function: (U) -> T) where U : T {
to write
uiState.updateIfSubtype<ContentState, _> { ... }
l
Oh that's nice, I didn't know this 🙂
Thanks!
m
Another option is to add abstract functions to the
MyScreenState
and let each state return an updated state based on its own logic. If there is no change it just returns itself.
l
I want to avoid writing the same state in this case (I'm not sure if compiler optimizes it away)