SharedFlow: Event Buses and One-Shot Events
Configure SharedFlow replay and extraBufferCapacity for event broadcasting.
SharedFlow: Event Buses and One-Shot Events is a free Kotlin Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is SharedFlow?
SharedFlow is a hot Flow that broadcasts to all active collectors. Unlike StateFlow, it has no concept of a current value — it is purely event-driven.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val events = MutableSharedFlow<String>()
launch { events.collect { println("Collector 1: $it") } }
launch { events.collect { println("Collector 2: $it") } }
delay(50)
events.emit("UserLoggedIn")
events.emit("DataRefreshed")
delay(50)
coroutineContext.cancelChildren()
}replay Parameter
replay buffers the last N emissions. New collectors immediately receive up to N past events. Default is 0 (no replay).
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = MutableSharedFlow<Int>(replay = 2)
flow.emit(1); flow.emit(2); flow.emit(3)
// Late subscriber gets last 2:
flow.collect { print("$it ") } // prints 2 3
}extraBufferCapacity
extraBufferCapacity adds buffer space beyond replay. Senders can emit without suspending until the buffer is full.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 10
)
// Can emit up to 10 times without a collector ready:
repeat(10) { flow.tryEmit(it) }
launch { flow.collect { print("$it ") } }
delay(50)
coroutineContext.cancelChildren()
}tryEmit for Non-Suspending Emit
tryEmit(value) emits without suspending, returning false if the buffer is full. Use in non-suspend contexts (callbacks, click handlers).
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = MutableSharedFlow<String>(extraBufferCapacity = 5)
val ok = flow.tryEmit("click") // non-suspending
println("Emitted: $ok")
launch { flow.collect { println(it) } }
delay(50)
coroutineContext.cancelChildren()
}One-Shot Events (UI Navigation)
Use SharedFlow with replay=0 for one-shot UI events like navigation or showing a Snackbar — events are not replayed on recomposition.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
class NavViewModel {
private val _events = MutableSharedFlow<String>()
val events = _events.asSharedFlow()
fun navigateTo(route: String) {
// viewModelScope.launch:
kotlinx.coroutines.GlobalScope.launch { _events.emit(route) }
}
}
// Collect in UI:
// viewModel.events.collect { route -> navController.navigate(route) }SharedFlow vs StateFlow
StateFlow: state with current value (UI state). SharedFlow: events without persistence (navigation, Snackbar, analytics). Choose based on whether consumers need the latest value.
import kotlinx.coroutines.flow.*
// StateFlow: always has a value; new subscribers get current value
val uiState = MutableStateFlow("idle")
// SharedFlow: no stored value; events fire and are gone (unless replay>0)
val singleEvents = MutableSharedFlow<String>(replay = 0)
fun main() { println("State = what it is; Event = what happened") }Event Bus with SharedFlow
Implement a simple app-wide event bus using a singleton SharedFlow, replacing RxJava PublishSubject patterns.
import kotlinx.coroutines.flow.*
object EventBus {
private val _events = MutableSharedFlow<Any>(extraBufferCapacity = 100)
val events = _events.asSharedFlow()
fun post(event: Any) { _events.tryEmit(event) }
}
sealed class AppEvent {
object UserLoggedOut : AppEvent()
data class ShowError(val msg: String) : AppEvent()
}
// Usage: EventBus.post(AppEvent.UserLoggedOut)subscriptionCount
sharedFlow.subscriptionCount is a StateFlow tracking the number of active collectors — useful for starting/stopping upstream producers.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = MutableSharedFlow<Int>()
println("Subscribers: ${flow.subscriptionCount.value}") // 0
val job = launch { flow.collect { } }
delay(50)
println("Subscribers: ${flow.subscriptionCount.value}") // 1
job.cancel()
delay(50)
println("Subscribers: ${flow.subscriptionCount.value}") // 0
}resetReplayCache
resetReplayCache() clears the buffered replay cache, useful when the replayed events are stale and new subscribers should not see them.
import kotlinx.coroutines.flow.*
fun main() {
val flow = MutableSharedFlow<Int>(replay = 3)
flow.tryEmit(1); flow.tryEmit(2); flow.tryEmit(3)
println("Replay cache: ${flow.replayCache}") // [1, 2, 3]
flow.resetReplayCache()
println("Replay cache: ${flow.replayCache}") // []
}Handling Back-Pressure in SharedFlow
When collectors are slow, use onBufferOverflow to choose: SUSPEND (default), DROP_OLDEST, or DROP_LATEST.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.channels.BufferOverflow
fun main() = runBlocking {
val flow = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 3,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
repeat(10) { flow.tryEmit(it) }
launch {
flow.collect { println(it) } // only sees most recent 3
}
delay(50)
coroutineContext.cancelChildren()
}Collecting with timeout
Collect a SharedFlow with a timeout to process a finite number of events and then stop, useful for testing or bounded processing.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val events = MutableSharedFlow<String>()
launch { repeat(5) { delay(50); events.emit("Event $it") } }
withTimeoutOrNull(200) {
events.collect { println(it) }
}
println("Done collecting")
}Quick Check
What replay value should you use for one-shot UI events like navigation?
Recap
SharedFlow is a hot event broadcaster. Use replay=0 for one-shot events, replay>0 for late subscribers. tryEmit for non-suspend contexts. Choose SharedFlow for events; StateFlow for state.
Frequently asked questions
Is the “SharedFlow: Event Buses and One-Shot Events” lesson free?
Yes — the full text of “SharedFlow: Event Buses and One-Shot Events” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.
What will I learn in “SharedFlow: Event Buses and One-Shot Events”?
Configure SharedFlow replay and extraBufferCapacity for event broadcasting. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Kotlin Academy?
No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “SharedFlow: Event Buses and One-Shot Events” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Kotlin Academy lesson?
Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- StateFlow: Hot State Holder for UI
- SharedFlow: Event Buses and One-Shot Events
- Converting Cold Flow to Hot with shareIn and stateIn
- Testing StateFlow and SharedFlow with Turbine