Skip to main content

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.

Interview-ready answer

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.