StateFlow represents the latest state and always has a current value. SharedFlow broadcasts values to active collectors and can be configured for replay and buffering.
Practical guidance
- Use StateFlow for renderable UI state.
- Use SharedFlow for broadcasts where replay semantics are intentional.
- Model navigation or messages carefully; persistent UI state is often safer than one-shot events.
- Collect with repeatOnLifecycle or collectAsStateWithLifecycle.
Working example
private val _state = MutableStateFlow(ScreenState())
val state = _state.asStateFlow()
private val _events = MutableSharedFlow<ScreenEvent>(extraBufferCapacity = 1)
val events = _events.asSharedFlow()
_state.update { it.copy(loading = false) }
_events.tryEmit(ScreenEvent.Saved)
Common mistakes
- A SharedFlow with replay can repeat navigation after recreation.
- StateFlow suppresses equal consecutive values.
- Collecting without lifecycle control keeps upstream work active unnecessarily.
Key takeaway
If a new collector needs the current answer immediately, start with StateFlow. Use SharedFlow when the stream itself—not a current value—is the abstraction.
