One Off the Slack: StateFlow or State?
Jorge Dominguez asked:
Checking the Jetnews sample app there’s this code snippet that allows state collection from a composable:
private val viewModelState = MutableStateFlow(HomeViewModelState(isLoading = true))
val uiState = viewModelState
.map { it.toUiState() }
.stateIn(
viewModelScope,
SharingStarted.Eagerly,
viewModelState.value.toUiState()
)
My question is, what’s the advantage of using
stateIn()
here? I would normally do something like:
private val _viewModelState = MutableStateFlow(HomeViewModelState(isLoading = true))
val viewModelState get() = _viewModelState
But looking into the
stateIn()
docs there’s a mention to increased performance for multiple observers since the upstream flow is instantiataed only once, but what if the flow is collected from a single composable? is there really an advantage there?
I was thinking that each recomposition can be considered as a new observer, in which case I can see how the use of
stateIn()
helps, but I’d like to fully understand the implications of its usage and how it’s better, so if anyone can shed some light I’d be grateful.
Google’s Adam Powell was unimpressed with that code:
it’s trying to satisfy conflicting requirements: the source of truth isn’t a
UiState
, it’s something that gets mapped to one, and if you don’t have aStateFlow
then you don’t have an initial value available, you have to subscribe and wait for it to emit, and in the case of consuming from compose that means you get a frame of empty data before the first item is known.
the whole thing could instead be written as:
private var viewModelState by mutableStateOf(HomeViewModelState(isLoading = true))
val uiState: UiState
get() = viewModelState.toUiState()
to leverage snapshots instead and skip all of the subscription complexity, since the upstream source of truth is a
MutableStateFlow
anyway - a hot data source that doesn’t care about subscription awareness in the first place.
Jorge wanted a bit more clarification, though, on part of his original question:
do you think the statement “…each recomposition can be considered as a new observer” is true?
And the answer to that is “no”, as Adam explained:
the call to
.collectAsState
is an observer for as long as it remains in composition with the same receiver flow instance
the same underlying observer (call to
Flow.collect
persists undisturbed across recompositions for as long as the call to.collectAsState
is present in the composition for the sameFlow
instance
Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!