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() }

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 a StateFlow 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 same Flow instance

Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!