Android Nomad #51 - Flow
Understanding Kotlin Flow, StateFlow, and SharedFlow: Choosing the Right Tool for Your App
Kotlin’s Flow API is a cornerstone of modern asynchronous programming, offering tools to manage sequential, stateful, and event-driven data streams effectively. This blog delves deep into Flow, StateFlow, and SharedFlow, their differences, and practical applications, incorporating examples and best practices.
1. Flow: The Foundation of Reactive Streams
What is Flow?
Flow represents a cold stream of sequential values that are asynchronously generated and emitted. Unlike suspending functions (which return a single value) or collections (which hold a set of values), a Flow emits values on demand, only when collected.
Flow Characteristics
Cold Nature: Values are not emitted until a terminal operator (like
collect) is applied.Operators: Intermediate operators like
mapandfiltertransform or react to emissions, while terminal operators likecollectandfirstfinalize the stream.Efficiency: A cold nature ensures resources are not wasted on unnecessary computations.
Creating and Using a Flow
Flow Builders
flow {}: Use for custom logic and emitting values explicitly.flowOf(...): Quickly create a flow from given values..asFlow(): Convert collections or ranges into a flow.
Example: Flow with Operators
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking
val numberFlow = flow {
emit(1)
emit(2)
}.map { value ->
"Transformed Value: $value"
}
runBlocking {
numberFlow.collect { value -> println(value) }
}2. StateFlow: Reactive State Management
What is StateFlow?
StateFlow is a hot stream that always holds the latest value and emits it to new collectors. It is ideal for managing and sharing state across components like ViewModels and UI layers.
Key Features
Hot Stream: Emits the latest state to all active collectors.
Atomic Updates: Safely update values using
.updateor.value.Thread Safety: Designed for concurrent environments.
Creating StateFlow
From MutableStateFlow
val mutableState = MutableStateFlow("Initial State")
val stateFlow = mutableState.asStateFlow()Using
stateIn
Convert a coldFlowtoStateFlowusingstateIn.
val myFlow = flowOf(1, 2, 3)
val stateFlow = myFlow.stateIn(
scope = coroutineScope,
started = SharingStarted.Lazily,
initialValue = 0
)Example: State Management with StateFlow
val mutableState = MutableStateFlow("Idle")
// Update state atomically
mutableState.update { currentState ->
"$currentState -> Active"
}
println(mutableState.value) // Output: "Idle -> Active"
// Collect state changes
mutableState.collect { state ->
println("New State: $state")
}3. SharedFlow: Event-Driven Programming
What is SharedFlow?
SharedFlow is a hot stream designed for broadcasting events. Unlike StateFlow, it does not hold a state but supports configurable replay for event propagation.
Key Features
Hot Stream: Events are broadcast to active collectors.
Replay Cache: Configurable to replay past events for new collectors.
Multiple Collectors: Emit events to many subscribers.
When to Use SharedFlow
Broadcasting UI events like navigation or snack bars.
Decoupling event producers and consumers.
Example: Event Broadcasting with SharedFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.runBlocking
val eventFlow = MutableSharedFlow<String>(replay = 2) // Cache last 2 events
runBlocking {
// Emit events
eventFlow.emit("Event 1")
eventFlow.emit("Event 2")
// Collect events
eventFlow.asSharedFlow().collect { event ->
println("Received: $event")
}
}Key Differences
Flow Operators: Intermediate and Terminal
Intermediate Operators
Transformations:
map: Converts data types.filter: Filters values based on conditions.onEach: Reacts to each emitted value.
Adding Emissions:
onStart: Emits values before the main stream starts.
Example: Intermediate Operators
(0..10).asFlow()
.onStart { emit(11) }
.filter { it % 2 == 0 }
.map { "Even Number: $it" }
.collect { println(it) }Terminal Operators
Non-Canceling:
collect: Reacts to each emitted value.toList: Collects all values into a list.
Canceling:
first: Collects the first value and cancels the stream.
Example: Terminal Operators
val firstValue = flowOf(1, 2, 3).first() // Output: 1
println(firstValue)Handling Exceptions
Use catch or inline try-catch for robust error handling.
Example: Exception Handling in Flow
flow {
emit(1)
throw Exception("Something went wrong")
}.catch { exception ->
emit("Error: ${exception.message}")
}.collect { value ->
println(value)
}Choosing the Right Tool
Use Flow for cold, sequential data streams (e.g., paginated API results).
Use StateFlow for state management (e.g., ViewModel state in Android).
Use SharedFlow for event broadcasting (e.g., navigation events).
By understanding these APIs and applying them effectively, you can build scalable and reactive Kotlin applications effortlessly.


