Careeroza
stateIn and shareIn
Cold to Hot
stateIn and shareIn turn a cold flow (e.g. a Room query) into a hot one that is shared by all collectors, so the database is queried once, not once per collector.
| SharingStarted | Upstream runs |
|---|---|
Eagerly | immediately, until the scope is cancelled |
Lazily | from the first subscriber, until the scope is cancelled |
WhileSubscribed(5000) | while someone collects, plus 5 s after the last one leaves (survives screen rotation) |
stateIn
Example
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun temperatureSensor(): Flow<Int> = flow {
println("Sensor started")
emit(28)
delay(100)
emit(31)
}
fun main() = runBlocking {
val temp: StateFlow<Int> = temperatureSensor()
.stateIn(this, SharingStarted.Eagerly, initialValue = 0)
println("Right away: ${temp.value}")
delay(50)
println("After 50 ms: ${temp.value}")
delay(100)
println("After 150 ms: ${temp.value}")
coroutineContext.cancelChildren()
}Output
Right away: 0 Sensor started After 50 ms: 28 After 150 ms: 31
shareIn: One Upstream, Many Collectors
Example
Two screens collect the same ticker, but the upstream starts only once.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val ticker = flow {
println("Upstream connected")
repeat(3) { delay(100); emit(it) }
}
val shared = ticker.shareIn(this, SharingStarted.WhileSubscribed(), replay = 0)
launch { shared.collect { println("Screen A got $it") } }
launch { shared.collect { println("Screen B got $it") } }
delay(450)
coroutineContext.cancelChildren()
}Output
Upstream connected Screen A got 0 Screen B got 0 Screen A got 1 Screen B got 1 Screen A got 2 Screen B got 2
In a ViewModel
class ProductsViewModel(repo: ProductRepository) : ViewModel() {
val products: StateFlow<List<Product>> = repo.observeProducts() // Flow from Room
.map { list -> list.sortedBy { it.price } }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
}