Flow
A Flow<T> describes asynchronous values over time. A cold flow normally starts its producer separately for each collector.
val numbers = flow {
emit(1)
emit(2)
}
Nothing runs until collection starts.
Operators
repository.search(query)
.map { users -> users.map(User::toUi) }
.distinctUntilChanged()
.catch { error -> emit(emptyList()) }
.collect { users -> render(users) }
catch handles upstream exceptions, not exceptions thrown after it by the collector.
flowOn
flowOn(Dispatchers.IO) changes the context of upstream operations. It does not move downstream collection after the operator.
Latest versus every value
collect: process every emission sequentially.collectLatest: cancel the previous collector block when a new value arrives.mapLatest/flatMapLatest: cancel obsolete transformations/inner streams, useful for search queries.
✕ Start a new search without cancelling the old one
queryFlow.collect { query -> launch { search(query) } }
✓ Keep only the latest query
queryFlow.flatMapLatest { query -> repository.search(query) }
Completion and cancellation
Flow follows coroutine cancellation. Use onCompletion for completion observation, but inspect its cause: normal completion, failure, and cancellation are different.
Flow is a cold asynchronous stream by default. Operators run in order, flowOn affects upstream context, catch catches upstream failures, and latest operators cancel obsolete work. Collection must be lifecycle-aware in Android UI.