๐ 02 ยท learn android
Kotlin Flow
Reactive streams on coroutines
A sequence of values over time, produced, transformed, and collected.
If a coroutine answers "run this work," a Flow answers "stream this data." A coroutine produces one result (or none, or throws); a Flow produces a sequence of values over time โ a live database query, a progress tick, a search box that emits as the user types. Flow is Kotlin's reactive-streams library, rebuilt on the coroutine machinery, so it inherits cancellation and structured concurrency for free.
Every Flow is three pieces wired together: a builder produces values, operators transform the stream, and a terminal operator collects it. Understand those three and every operator is just an instance of one of them.
The mental model
How it fits together
The pipeline โ producer โ transformer โ consumer
nothing runs until the terminal operator subscribes
Cold vs hot
cold โ flow{ }
runs once per collector, from the start
hot โ StateFlow
emits regardless; late collectors see latest
instant search = debounce + filter + distinctUntilChanged + flatMapLatest
Key concepts
The ideas to internalise
Cold by default
A flow { } doesn't run when you build it โ only when something collects it, and once per collector. No background work unless a consumer is listening.
Builders
flow { emit(x) } for imperative logic, flowOf(1,2,3) for a fixed set, asFlow() to lift a collection, callbackFlow to bridge a callback API.
Operators
map, filter, debounce, zip, flatMapLatest, retry โ transform the stream between builder and collector. Operators are themselves flows that collect upstream and emit downstream.
Terminal operators
collect, toList, first, reduce โ what ends the stream. Nothing runs until one subscribes.
StateFlow / SharedFlow
The hot flows. StateFlow holds one value for UI state and conflates updates; SharedFlow broadcasts events. Late collectors see the latest, not history.
Backpressure
A slow collector makes the producer wait by default. Use buffer, conflate, or collectLatest to say explicitly how to handle a producer that outpaces the collector.
Example index
17 runnable examples
Each is a self-contained Activity + ViewModel pair you can open, read, and run.
- 01Single Network Callthe simplest Flow network call
- 02Series Network Callsdependent calls in sequence
- 03Parallel Network Callsindependent calls combined with zip
- 04Room DB OperationFlow with Room
- 05Long Running Taska background task as a Flow
- 06Two Long Running Taskstwo tasks in parallel
- 07Catch Error Handlingthe catch operator
- 08EmitAll Error Handlingrecovering with a fallback flow
- 09CompletiononCompletion
- 10Reducethe reduce operator
- 11Mapthe map operator
- 12Filterthe filter operator
- 13Search Featureinstant search: debounce + filter + distinctUntilChanged + flatMapLatest
- 14Retrythe retry operator
- 15RetryWhenconditional retry
- 16Retry with Exponential Backoffbacking off between retries
- 17Unit Testtesting a Flow-based ViewModel
The takeaway
The entire instant-search feature is four operators chained: debounce (wait for a pause) + filter (drop empties) + distinctUntilChanged (dedupe) + flatMapLatest (only the latest). Once you can read a chain like that, you can read any Flow.