SharedFlow vs StateFlow
StateFlow and SharedFlow seem similar, but they are designed for different use cases, and they should not be confused. Let's discuss the key differences and usages.
Let's start with how they work. SharedFlow resembles a broadcast channel—emitted values are delivered to all observers. It allows setting a replay parameter specifying how many past values should be emitted to new observers.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
suspend fun main(): Unit = withTimeoutOrNull(3000) {
val sf = MutableSharedFlow<String>(replay = 1)
sf.emit("Message-1")
sf.emit("Message0")
sf.onEach { println("#1 $it") }.launchIn(this)
// #1 Message0
sf.onEach { println("#2 $it") }.launchIn(this)
// #2 Message0
delay(1000)
sf.emit("Message1") // #1 Message1 #2 Message1
sf.emit("Message2") // #1 Message2 #2 Message2
}
StateFlow behaves a lot like SharedFlow with reply = 1, but StateFlow must always have a value, so an initial value must be specified when a StateFlow is created. This value can be accessed or changed using value property.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
suspend fun main(): Unit = withTimeoutOrNull(3000) {
val sf = MutableStateFlow("Message0")
sf.onEach { println("#1: $it ") }.launchIn(this)
// #1: Message0
sf.onEach { println("#2: $it ") }.launchIn(this)
// #2: Message0
delay(1000)
sf.value = "Message1" // #1: Message1 #2: Message1
sf.emit("Message2") // #1: Message2 #2: Message2
println(sf.value) // Message2
}
But StateFlow was designed for a concrete use case: to represent an observable state. In Android, it is used to represent the state of our application, and views observe it and redraw whenever it changes. That is the key source of differences.
Redrawing view can be expensive, and state changes can be frequent. That is why two optimizations were introduced. First, updates are only emitted when they are different from the previous state. This behavior can be achieved on SharedFlow using distinctUntilChanged.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
suspend fun main(): Unit = withTimeoutOrNull(3000) {
val state = MutableStateFlow("A")
state.onEach { println("Updated to $it") }
.stateIn(this) // Updated to A
state.value = "B" // Updated to B
state.value = "B" // (nothing printed)
state.emit("B") // (nothing printed)
}
Second, StateFlow is conflated, meaning if observer is slower than value changes, it might lose some intermediate updates. That is appropriate for StateFlow, because we are not interested in drawing obsolete state.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
suspend fun main(): Unit = withTimeoutOrNull(3000) {
val state = MutableStateFlow('X')
launch {
for (c in 'A'..'E') {
delay(300)
state.value = c
// or state.emit(c)
}
}
state.collect {
delay(1000)
println(it)
}
}
// X
// C
// E
StateFlow also have some tools for state update, like update function, that lets us safely update state, bu creating a new one based on the current one.
import kotlinx.coroutines.*
suspend fun main(): Unit = withTimeoutOrNull(3000) {
val sf = MutableStateFlow(0)
coroutineScope {
repeat(10_000) {
launch {
sf.update { it + 1 }
}
}
}
println(sf.value) // 10000
}
That is why StateFlow should be used concretely to represent the observable state of our application. It should not be used as a "boradcast channel". For that we use SharedFlow.
Let's see an Android example: Things like a progress bar or data to display should be represented by StateFlow, but exceptions or messages to show to users as toasts should be represented as SharedFlow.
Now let's see a backend example. On a backend service, updates received from some websocket should be represented using SharedFlow, but an observable cuter of application users can be represented using StateFlow.
class InventoryService {
private val inventoryState = MutableStateFlow<InventoryState>(InventoryState.Loading)
private val inventoryEvents = MutableSharedFlow<InventoryEvent>()
// ...
}
class StatusTrackingService {
val activeConnections = MutableStateFlow(0)
val highTrafficEvents = MutableSharedFlow<Int>()
// ...
}
To sum up, StateFlow and SharedFlow are similar, but they are designed for different use cases. StateFlow is for representing observable state, and SharedFlow is for broadcasting events. Use them accordingly.
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, is known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.