Flow: Reactive Streams
From one value to many
A suspend function returns a single value once. But often you need a stream of values over time: live search results, a database that updates, a timer. Flow is Kotlin’s tool for this — an asynchronous sequence you can transform and collect.
Creating a flow
fun countdown(from: Int): Flow<Int> = flow {
for (i in from downTo 1) {
emit(i) // send a value
delay(1000)
}
}
Collecting it
lifecycleScope.launch {
countdown(3).collect { value ->
println(value) // 3, 2, 1 (one per second)
}
}
A flow is “cold”: nothing runs until you collect it.
Transforming flows
Flows support the same readable operators as collections:
searchQueries
.debounce(300) // wait for the user to stop typing
.filter { it.length > 2 }
.map { query -> api.search(query) }
.collect { results -> show(results) }
StateFlow: holding the latest value
For UI state you usually want to always have a current value. StateFlow is a “hot” flow that holds the latest value — ideal in a ViewModel.
class SearchViewModel : ViewModel() {
private val _state = MutableStateFlow<UiState>(UiState.Idle)
val state: StateFlow<UiState> = _state // read-only to the UI
fun search(q: String) {
viewModelScope.launch {
_state.value = UiState.Loading
_state.value = UiState.Success(api.search(q))
}
}
}
Flow vs StateFlow
- Flow — cold, starts on collect, good for one-shot streams (a network response stream, a DB query).
- StateFlow — hot, always has a current value, perfect for observable UI state.
Common mistakes
- Expecting a cold
Flowto run without collecting it. - Exposing a
MutableStateFlowpublicly — expose the read-onlyStateFlow. - Collecting a flow without a lifecycle-aware scope, wasting work when the screen is hidden.
Summary: UseFlowfor streams of values over time andStateFlowto hold and expose the latest UI state. Transform them with the same operators you use on collections.