SharedFlow is a hot, broadcast-style flow in Kotlin used to send events or data to multiple collectors at the same time.
Unlike cold flows, SharedFlow does not depend on a single collector.
It keeps emitting values even when no one is listening.
A cold flow starts producing values only when collected. But SharedFlow starts emitting immediately after creation.
val coldFlow = flow {
emit(1)
emit(2)
emit(3)
}
coldFlow.collect { println("Collector: $it") } // Starts fresh
val shared = MutableSharedFlow()
shared.emit(1)
shared.emit(2)
shared.collect { println("A: $it") } // Does NOT receive past values
Output: (collector A receives nothing because no replay is set)
val sf = MutableSharedFlow()
launch {
delay(500)
sf.collect { println("Collector 1: $it") }
}
launch {
repeat(3) {
delay(300)
println("Emitting $it")
sf.emit(it)
}
}
Emitting 0
Emitting 1
Collector 1: 1
Emitting 2
Collector 1: 2
Explanation: Collector starts late, so it misses older emissions.
val sf = MutableSharedFlow()
launch {
sf.collect { println("Collector A: $it") }
}
launch {
sf.collect { println("Collector B: $it") }
}
launch {
repeat(3) {
sf.emit(it)
}
}
Collector A: 0
Collector B: 0
Collector A: 1
Collector B: 1
Collector A: 2
Collector B: 2
Both collectors receive the same values simultaneously. SharedFlow broadcasts items.
SharedFlow does not slow down producers. If one collector is slow, the other collectors continue normally.
val sf = MutableSharedFlow()
launch {
sf.collect {
delay(300)
println("Slow Collector: $it")
}
}
launch {
sf.collect {
println("Fast Collector: $it")
}
}
launch {
repeat(3) {
println("Emit $it")
sf.emit(it)
}
}
Emit 0
Fast Collector: 0
Emit 1
Fast Collector: 1
Emit 2
Fast Collector: 2
Slow Collector: 0
Slow Collector: 1
Slow Collector: 2
The slow collector lags behind, but SharedFlow stores nothing unless replay is set. All emissions are delivered, but slow collectors may process late.
No, in this case the slow collector does not miss any values. Even though MutableSharedFlow has replay = 0, it still delivers all emissions to all actively collecting collectors.
val sf = MutableSharedFlow()
launch {
sf.collect {
delay(300)
println("Slow Collector: $it")
}
}
launch {
sf.collect {
println("Fast Collector: $it")
}
}
launch {
repeat(3) {
println("Emit $it")
sf.emit(it)
}
}
Emit 0 Fast Collector: 0 Emit 1 Fast Collector: 1 Emit 2 Fast Collector: 2 Slow Collector: 0 Slow Collector: 1 Slow Collector: 2
Even though the slow collector processes each value with a delay, it does not lose values.
A SharedFlow without replay does not store values for future collectors, but it does deliver emissions to all currently active collectors.
Because both collectors are already collecting when emissions start, both receive all values. The fast collector prints immediately, while the slow collector prints later due to its delay.
If you start collecting after the emissions, then without replay you would miss values. But in this example, both collectors are active before emission, so no value is lost.
You can replay the last n values using:
MutableSharedFlow(replay = n)
val sf = MutableSharedFlow(replay = 1)
sf.emit(10)
sf.emit(20)
sf.collect { println("Collector A: $it") }
Collector A: 20
New collectors receive the last emission automatically.
SharedFlow is commonly used in MVVM for UI events such as:
ViewModels use encapsulation to expose read-only SharedFlow to the UI.
class MyViewModel : ViewModel() {
private val _events = MutableSharedFlow(replay = 0)
val events: SharedFlow get() = _events
fun sendEvent(msg: String) {
viewModelScope.launch {
_events.emit(msg)
}
}
}
lifecycleScope.launch {
viewModel.events.collect {
println("UI Event: $it")
}
}
viewModel.sendEvent("Login Successful")
UI Event: Login Successful
The UI cannot modify the SharedFlow directly because the setter is private. This is perfect encapsulation.
replay allows sending old values to new collectors.