Sparround

Flow, StateFlow, SharedFlow — hot vs cold

A Flow is an asynchronous stream of values: where a suspend function returns one value, a Flow emits a sequence.

The key split is cold vs hot:

  • Cold flows (plain flow { }) restart for every collector. With no collector, nothing runs. Example: a Room query returning Flow.
  • Hot flows (StateFlow, SharedFlow) live and emit independently of collectors; a new collector joins "mid-stream".

StateFlow — a hot flow that always holds a current value: readable via value, new collectors immediately get the latest value, consecutive equal values are skipped (conflation + distinct). The standard for UI state.

SharedFlow — a hot flow holding no value, configurable (replay, extraBufferCapacity). Used for one-shot events (navigation, snackbars).

PropertyFlow (cold)StateFlowSharedFlow
Current valueNoneAlways (initial required)None (replay configurable)
Runs without collectors?NoYesYes
Emits duplicate values?YesNo (filtered via equals)Yes
Typical useRepository/DB streamsUI stateOne-shot events

Interview trap — StateFlow for events: keep a snackbar message in StateFlow and, after rotation, the new collector re-receives the last value and the snackbar shows again. For one-shot events use SharedFlow(replay = 0) or a Channel. And collect in the UI inside repeatOnLifecycle(Lifecycle.State.STARTED) — otherwise collection continues in the background.

🛠 Practice task

Create one MutableStateFlow<String> and one MutableSharedFlow<String> (replay = 0).

  • Emit to both, then attach a collector — note which value survives.
  • Emit the same value twice to the StateFlow — how many times does the collector fire?
  • Write a cold flow { }, attach two collectors and watch the emissions repeat.

Done when: you can answer "why can't a snackbar message live in a StateFlow" from what you just saw.

📚 Sources and documentation