StateFlow, SharedFlow, and Channels
Prerequisites: Flow: asynchronous streams
Regular flows are cold and start fresh for each collector. Apps also need hot streams that exist independently of collectors — like "the current state of this screen".
StateFlow — observable state
A StateFlow always holds one current value. New collectors receive
the latest value immediately, then every change:
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class CounterModel {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() {
_count.update { it + 1 }
}
}
fun main() = runBlocking {
val model = CounterModel()
val job = launch {
model.count.collect { println("Count: $it") }
}
delay(50)
model.increment()
delay(50)
model.increment()
delay(50)
job.cancel()
println("Final: ${model.count.value}")
}
Count: 0
Count: 1
Count: 2
Final: 2
- The class keeps a private
MutableStateFlowand exposes a read-onlyStateFlow— only the model can change the state. update { }changes the value safely, even from several threads..valuereads the current value directly.
This is exactly how Android ViewModels expose UI state: the screen
collects the StateFlow and redraws whenever it changes.
SharedFlow — events for many listeners
A SharedFlow broadcasts values to all current collectors, without
holding a "current" value (unless configured to replay some). Use it for
events such as "show a message" or "navigate to checkout".
Channels — hand values between coroutines
A Channel is a queue between coroutines: one sends, another receives,
and each value is delivered to exactly one receiver:
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val jobs = Channel<Int>()
launch {
for (n in 1..3) jobs.send(n)
jobs.close()
}
for (job in jobs) {
println("Processing job $job")
}
}
Processing job 1
Processing job 2
Processing job 3
for (job in jobs) receives until the channel is closed. Several
coroutines reading from one channel form a worker pool.
Choosing
| Need | Use |
|---|---|
| Current state that UI observes | StateFlow |
| Broadcast events to several listeners | SharedFlow |
| A work queue between coroutines | Channel |
| A sequence of values computed on demand | Flow |