Kotlin StateFlow and SharedFlow
Regular Flow is cold — it only runs when collected. StateFlow and SharedFlow are hot flows that are always active, can have multiple collectors at the same time, and emit values regardless of whether anyone is listening. They are the foundation of reactive UI state in Android apps.
Cold Flow vs Hot Flow
Cold Flow (regular flow):
Each collector gets its own independent sequence.
Flow only runs when collected.
Like a YouTube video — each viewer starts from the beginning.
Hot Flow (StateFlow / SharedFlow):
All collectors share the same stream of values.
Values are emitted regardless of collectors.
Like a live TV broadcast — viewers join mid-stream.
StateFlow — State Holder
StateFlow always holds a current value. New collectors immediately receive the latest value. It replaces LiveData in modern Android development.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
// MutableStateFlow holds and updates state
val counter = MutableStateFlow(0)
// Collector 1 — receives all updates
val job = launch {
counter.collect { value ->
println("Collector: count = $value")
}
}
delay(100)
counter.value = 1 // update state
delay(100)
counter.value = 2
delay(100)
counter.value = 3
delay(100)
job.cancel()
}Output:
Collector: count = 0
Collector: count = 1
Collector: count = 2
Collector: count = 3StateFlow in Android ViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
data class UiState(val loading: Boolean = false, val data: String = "", val error: String? = null)
class MainViewModel : ViewModel() {
// Private mutable — only ViewModel can change it
private val _uiState = MutableStateFlow(UiState())
// Public read-only — Activity/Fragment collects this
val uiState: StateFlow = _uiState.asStateFlow()
fun loadData() {
viewModelScope.launch {
_uiState.value = UiState(loading = true)
try {
val result = fetchFromServer()
_uiState.value = UiState(data = result)
} catch (e: Exception) {
_uiState.value = UiState(error = e.message)
}
}
}
private suspend fun fetchFromServer(): String {
kotlinx.coroutines.delay(1000)
return "Hello from server!"
}
}
// In Activity:
// lifecycleScope.launch {
// viewModel.uiState.collect { state ->
// when {
// state.loading -> showSpinner()
// state.error != null -> showError(state.error)
// else -> showData(state.data)
// }
// }
// } SharedFlow — Event Bus
SharedFlow does not hold a single current value. It is used for one-time events like navigation, toasts, and dialogs where replaying the last value would be wrong.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val events = MutableSharedFlow()
// Collector 1
val job1 = launch {
events.collect { println("Collector 1: $it") }
}
// Collector 2
val job2 = launch {
events.collect { println("Collector 2: $it") }
}
delay(100)
events.emit("User logged in")
delay(100)
events.emit("Order placed")
delay(100)
job1.cancel()
job2.cancel()
} Output:
Collector 1: User logged in
Collector 2: User logged in
Collector 1: Order placed
Collector 2: Order placedStateFlow vs SharedFlow
StateFlow SharedFlow
───────────────────────────────────────────────────────
Has current value? YES (always) NO
New collector gets? Latest value Nothing (by default)
Replay? Always 1 value Configurable (replay = N)
Emit same value? Skipped (dedup) Always emitted
Use for? UI state One-time events
SharedFlow with Replay
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
// replay = 2 means new collectors get the last 2 emitted values
val notifications = MutableSharedFlow(replay = 2)
notifications.emit("App started")
notifications.emit("Database ready")
notifications.emit("Server connected")
// Late collector — receives last 2 replayed values
notifications.collect { println("Late collector: $it") }
} Output:
Late collector: Database ready
Late collector: Server connectedPractical Example: Cart State Manager
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
data class CartState(val items: List = emptyList(), val total: Int = 0)
class CartStore {
private val _state = MutableStateFlow(CartState())
val state: StateFlow = _state.asStateFlow()
private val _events = MutableSharedFlow()
val events: SharedFlow = _events.asSharedFlow()
suspend fun addItem(item: String, price: Int) {
val current = _state.value
_state.value = CartState(current.items + item, current.total + price)
_events.emit("Added: $item (₹$price)")
}
}
fun main() = runBlocking {
val cart = CartStore()
launch { cart.state.collect { println("State → items=${it.items.size}, total=₹${it.total}") } }
launch { cart.events.collect { println("Event → $it") } }
delay(50)
cart.addItem("Laptop", 75000)
cart.addItem("Mouse", 800)
delay(200)
} 