Asynchronous Streams with Flow
A Flow is a cold asynchronous stream of values — like a suspending
Sequence. It produces values over time and only runs when collected.
Building a flow
import kotlinx.coroutines.flow.*
fun numbers(): Flow<Int> = flow {
for (i in 1..3) {
delay(100)
emit(i) // emit a value
}
}
Other builders: flowOf(1, 2, 3) and listOf(1, 2, 3).asFlow().
Collecting
A flow does nothing until collected (in a coroutine):
numbers().collect { value ->
println(value) // 1, 2, 3 — one every 100 ms
}
Operators
Flows have the same functional operators as collections, plus async-aware ones:
numbers()
.map { it * it }
.filter { it % 2 == 1 }
.toList() // terminal: [1, 9]
Intermediate operators (map, filter, take) are lazy; terminal operators
(collect, toList, first, reduce) trigger collection.
Cold vs hot: StateFlow and SharedFlow
A cold flow restarts for each collector. Hot flows stay active and share emissions:
StateFlow— always holds the latest value; great for UI state.SharedFlow— broadcasts events to multiple collectors.
val state = MutableStateFlow(0)
state.value = 1 // update
val current = state.value // read the latest
state.update { it + 1 } // atomic update
StateFlow is the backbone of modern Android UI state — you will use it heavily
in Part 8.
Flow context
A flow’s emission runs in the collector’s context by default. Move upstream work
with flowOn:
dataFlow()
.map { heavyTransform(it) }
.flowOn(Dispatchers.Default) // upstream runs on Default
.collect { render(it) } // collection stays on the caller's context
Exercises
-
Create a
Flow<Int>of 1..5, square each value, and collect the results into a list.Solution
suspend fun squaredList(): List<Int> =
(1..5).asFlow().map { it * it }.toList()
</details>
- Use a
MutableStateFlow<Int>as a counter and update it withupdate.
This solution is in examples/core/part6/ and is tested by CI.
Previous: Structured Concurrency · Next: Channels