StateFlow, SharedFlow, and channels
StateFlow: current state
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
StateFlow always has a current value, replays the latest value to new collectors, and conflates equal values using equality.
Update atomically when based on old state:
_uiState.update { old -> old.copy(count = old.count + 1) }
SharedFlow: broadcast values
SharedFlow has configurable replay and buffer behavior. It is useful when several collectors should receive the same stream and there is no single current-state meaning.
Channel: send/receive queue
A channel coordinates senders and receivers. Capacity controls whether send suspends, buffers, or replaces values. Closing a channel is meaningful; SharedFlow does not complete normally in the same way.
Events are a product decision
Navigation, snackbars, and payments are not solved merely by choosing SharedFlow or Channel. Decide whether an event must survive collector absence, configuration change, process death, or duplicate delivery.
✕ Use state as a one-time event without consumption design
data class UiState(val navigateToDetails: Boolean)
✓ Model durable destination state or explicit event semantics
data class UiState(val selectedUserId: UserId?)
Comparison
| Type | Has current value | Replays | Typical meaning |
|---|---|---|---|
| StateFlow | Yes | Latest one | Observable state |
| SharedFlow | No required current value | Configurable | Broadcast values/events |
| Channel | Queue semantics | Receive consumes | Point-to-point communication |
StateFlow represents current state and always has a value. SharedFlow broadcasts values with configurable replay/buffering. Channel provides send/receive queue semantics. Choose based on delivery guarantees, not naming convention.