SharedFlow: wat is het, SharedFlow vs StateFlow in Android

Auteur: IT Sectr Gepubliceerd: 2026-02-20 Leestijd: 9 min

SharedFlow — een hete reactieve stroom uit de Kotlin Coroutines-bibliotheek, geoptimaliseerd voor eenmalige gebeurtenissen (one-shot events) die niet mogen worden herhaald bij het draaien van het scherm of het opnieuw aanmaken van een abonnee. We laten zien hoe SharedFlow verschilt van StateFlow: in tegenstelling tot StateFlow slaat SharedFlow de laatste waarde niet op voor nieuwe abonnees en ondersteunt het configuratie van replay, extraBufferCapacity en onBufferOverflow. Volgens Google (Android Developers, 2025) is SharedFlow de aanbevolen oplossing voor navigatieopdrachten, Snackbar-berichten en andere gebeurtenissen die precies één keer moeten worden verwerkt.

Belangrijkste

  • SharedFlow — hot flow voor eenmalige gebeurtenissen: nieuwe abonnees krijgen zonder replay-instelling geen eerdere waarden.
  • MutableSharedFlow — wijzigbare versie met methoden emit() en tryEmit() voor het verzenden van gebeurtenissen.
  • SharedFlow vs StateFlow: SharedFlow conflueert waarden niet (kan meerdere bufferen), vereist geen beginwaarde, geschikt voor eenmalige gebeurtenissen.
  • replay — aantal laatste gebeurtenissen dat wordt afgespeeld voor nieuwe abonnees (standaard 0).
  • extraBufferCapacity — extra buffer voor gebeurtenissen bovenop replay, voorkomt blokkering van emit().

Wat is SharedFlow in Kotlin?

SharedFlow — is een hete stroom (hot flow) uit de kotlinx.coroutines.flow-bibliotheek die, in tegenstelling tot StateFlow, niet aan één enkele toestand is gebonden en een willekeurig aantal gebeurtenissen naar willekeurige abonnees kan emitteren. SharedFlow is het basistype voor StateFlow — StateFlow is geïmplementeerd via SharedFlow met replay = 1.

Kernkenmerk van SharedFlow — het hoeft de laatste waarde niet op te slaan. Standaard (replay = 0) ontvangt een nieuwe abonnee niets totdat er een nieuwe gebeurtenis wordt verzonden. Dit maakt SharedFlow ideaal voor scenario's waarin een gebeurtenis precies één keer moet worden verwerkt: navigatie, Snackbar, systeemmeldingen, QR-code scanresultaten.

SharedFlow is gestabiliseerd in kotlinx.coroutines 1.4.0 (november 2020) samen met StateFlow. Volgens de Kotlin Coroutines-documentatie (2025) gebruikt SharedFlow fijnmazige vergrendeling voor synchronisatie van abonnees en biedt het lineaire schaalbaarheid tot 1000+ gelijktijdige abonnees zonder prestatievermindering, bevestigd door JetBrains-tests.

SharedFlow vs StateFlow: wanneer wat gebruiken

De keuze tussen SharedFlow en StateFlow hangt af van de semantiek van de verzonden gegevens: toestand (StateFlow) of gebeurtenis (SharedFlow). Hieronder duidelijke criteria met voorbeelden.

CriteriumSharedFlowStateFlow
SemantiekEenmalige gebeurtenissen (navigatie, toast, alert)UI-toestand (lijst, laden, fout)
BeginwaardeNiet vereistVerplicht
Herhaling bij abonnementAlleen als replay > 0Altijd laatste waarde
ConflatieNee — gebeurtenissen gaan niet verloren (als buffer niet vol is)Ja — slaat alleen laatste op
BufferingConfigureerbaar via replay + extraBufferCapacityAlleen 1 (replay=1 vast)
GebruiknavigationEvent, showSnackbar, openDialogitems, isLoading, uiState

De eenvoudigste regel: als gegevens zichtbaar moeten zijn bij het draaien van het scherm — dan is het toestand (StateFlow). Als bij het draaien van het scherm de gebeurtenis niet mag worden herhaald — dan is het een eenmalige gebeurtenis (SharedFlow). Bijvoorbeeld „toast met foutmelding“ — SharedFlow: bij draaien mag de toast niet opnieuw verschijnen. „Productlijst“ — StateFlow: bij draaien moet de lijst op het scherm blijven.

Bij IT Sectr gebruiken we SharedFlow voor: navigatieopdrachten (overgang naar scherm, openen van diepe link), UI-gebeurtenissen (Snackbar, AlertDialog), systeemmeldingen (achtergrondgegevens bijwerken, betalingsresultaat), analytische gebeurtenissen (loggen, tracking).

MutableSharedFlow: emit, tryEmit en buffering

MutableSharedFlow — wijzigbare versie van SharedFlow met methoden emit() (suspend) en tryEmit() (non-suspend) voor het verzenden van gebeurtenissen. emit() onderbreekt als de buffer vol is en onBufferOverflow = SUSPEND. tryEmit() retourneert Boolean — of de gebeurtenis succesvol aan de buffer is toegevoegd.

kotlin
class EventBus {
    private val _events = MutableSharedFlow<UiEvent>(
        replay = 0,
        extraBufferCapacity = 10,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val events: SharedFlow<UiEvent> get() = _events

    suspend fun sendEvent(event: UiEvent) {
        _events.emit(event)
    }

    fun trySendEvent(event: UiEvent): Boolean {
        return _events.tryEmit(event)
    }
}

sealed interface UiEvent {
    data class ShowSnackbar(val message: String) : UiEvent
    data class NavigateTo(val route: String) : UiEvent
    data class ShowDialog(val title: String, val message: String) : UiEvent
}

De constructorparameters zijn cruciaal: replay = 0 garandeert dat de gebeurtenis niet wordt herhaald voor een nieuwe abonnee; extraBufferCapacity = 10 — buffer voor snelle verzending van gebeurtenissen voordat de UI zich abonneert; DROP_OLDEST — strategie bij overloop: oude gebeurtenissen worden verwijderd, nieuwe worden bewaard. Volgens Kotlin Coroutines Performance (JetBrains, 2024) verwerkt SharedFlow met extraBufferCapacity = 64 meer dan 100.000 gebeurtenissen per seconde zonder verlies.

SharedFlow voor eenmalige gebeurtenissen: het Event-patroon

Het Event-patroon (of UiEvent) — de door Google aanbevolen manier om eenmalige gebeurtenissen van ViewModel naar View te sturen. In tegenstelling tot toestand (StateFlow) moet de gebeurtenis precies één keer worden verwerkt en bij het draaien van het scherm niet worden herhaald. SharedFlow met replay = 0 is hiervoor ideaal.

kotlin
class CheckoutViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<CheckoutState>(CheckoutState.Idle)
    val uiState: StateFlow<CheckoutState> get() = _uiState

    private val _event = MutableSharedFlow<CheckoutEvent>()
    val event: SharedFlow<CheckoutEvent> get() = _event

    fun placeOrder() {
        viewModelScope.launch {
            _uiState.value = CheckoutState.Loading
            try {
                val orderId = orderRepository.createOrder(cart)
                _uiState.value = CheckoutState.Success(orderId)
                _event.emit(CheckoutEvent.NavigateToOrderTracking(orderId))
            } catch (e: Exception) {
                _uiState.value = CheckoutState.Error(e.message)
                _event.emit(CheckoutEvent.ShowErrorSnackbar(e.message ?: "Opmaakfout"))
            }
        }
    }
}

sealed interface CheckoutEvent {
    data class NavigateToOrderTracking(val orderId: String) : CheckoutEvent
    data class ShowErrorSnackbar(val message: String) : CheckoutEvent
}

In View (Activity/Fragment): abonnement op de gebeurtenis moet worden uitgevoerd in lifecycleScope met repeatOnLifecycle(STATE.STARTED). Bij elke toegang tot STARTED wordt het abonnement opnieuw aangemaakt, maar de gebeurtenis wordt niet herhaald omdat SharedFlow met replay=0 deze al heeft vrijgegeven. Dit garandeert dat navigatie naar het ordertrackingsscherm slechts één keer plaatsvindt, niet bij elke draaiing.

SharedFlow parameters: replay, extraBufferCapacity, onBufferOverflow

De constructor van MutableSharedFlow ontvangt drie parameters die het buffergedrag bepalen. Onjuiste configuratie kan leiden tot verlies van gebeurtenissen of blokkering van emit().

ParameterTypeStandaardBeschrijving
replayInt0Aantal laatste gebeurtenissen dat wordt afgespeeld voor een nieuwe abonnee. 0 = niet afspelen, 1 = als StateFlow
extraBufferCapacityInt0Extra buffer bovenop replay. Gebeurtenissen worden opgeslagen in een ringbuffer. 64 — aanbevolen limiet voor de meeste scenario's
onBufferOverflowBufferOverflowSUSPENDStrategie bij bufferoverloop: SUSPEND, DROP_OLDEST, DROP_LATEST
kotlin
// Configuraties voor verschillende scenario's:

// 1. Eenmalige UI-gebeurtenissen (navigatie, toasts)
val uiEvents = MutableSharedFlow<UiEvent>(
    replay = 0,
    extraBufferCapacity = 5,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

// 2. Replay-stroom voor toestandssynchronisatie (zoals StateFlow)
val stateLike = MutableSharedFlow<AppState>(
    replay = 1,
    extraBufferCapacity = 0
)

// 3. Hoogfrequente gebeurtenisverzending (analytics, logs)
val analytics = MutableSharedFlow<AnalyticsEvent>(
    replay = 0,
    extraBufferCapacity = 100,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

Belangrijk: extraBufferCapacity + replay = totale buffergrootte. Als emit() sneller wordt aangeroepen dan de abonnee de gebeurtenis verwerkt, raakt de buffer vol en wordt onBufferOverflow geactiveerd. Voor UI-gebeurtenissen is DROP_OLDEST een veilige strategie: oude gebeurtenissen (niet meer actuele navigaties) worden verwijderd ten gunste van nieuwe. Gebruik voor financiële transacties SUSPEND — dit garandeert dat geen enkele gebeurtenis verloren gaat ten koste van het blokkeren van de afzender.

Code voorbeelden: SharedFlow in Kotlin

Voorbeeld 1: SharedFlow voor navigatie met Jetpack Navigation

Navigatieopdrachten — klassiek use case voor SharedFlow. Fragment abonneert zich op gebeurtenissen en voert navigatie uit. Bij het draaien van het scherm wordt de opdracht niet herhaald.

kotlin
// ViewModel
class AuthViewModel : ViewModel() {
    private val _navEvent = MutableSharedFlow<NavEvent>()
    val navEvent: SharedFlow<NavEvent> get() = _navEvent

    fun onLoginSuccess() {
        viewModelScope.launch {
            _navEvent.emit(NavEvent.NavigateTo(NavRoutes.HOME))
        }
    }

    fun onLogout() {
        viewModelScope.launch {
            _navEvent.emit(NavEvent.NavigateTo(NavRoutes.LOGIN))
        }
    }
}

sealed interface NavEvent {
    data class NavigateTo(val route: String) : NavEvent
    data class NavigateBack(val popUpTo: String? = null) : NavEvent
}

// In Fragment:
viewLifecycleOwner.lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.navEvent.collect { navEvent ->
            when (navEvent) {
                is NavEvent.NavigateTo -> findNavController().navigate(navEvent.route)
                is NavEvent.NavigateBack -> findNavController().popBackStack()
            }
        }
    }
}

Voorbeeld 2: SharedFlow met Room en Flow-operators

Complex scenario: SharedFlow voor meldingen over achtergrondgebeurtenissen gecombineerd met StateFlow voor UI.

kotlin
class NotificationViewModel : ViewModel() {
    private val _toastMessage = MutableSharedFlow<String>()
    val toastMessage: SharedFlow<String> get() = _toastMessage

    private val _notifications = MutableStateFlow<List<Notification>>(emptyList())
    val notifications: StateFlow<List<Notification>> get() = _notifications

    init {
        viewModelScope.launch {
            notificationChannel
                .consumeAsFlow()
                .collect { notification ->
                    _notifications.value = _notifications.value + notification
                    _toastMessage.emit("Nieuwe melding: ${notification.title}")
                }
        }
    }

    fun dismissNotification(id: String) {
        _notifications.value = _notifications.value.filter { it.id != id }
    }

    fun markAllRead() {
        viewModelScope.launch {
            _notifications.value = _notifications.value.map { it.copy(isRead = true) }
            _toastMessage.emit("Alle meldingen gemarkeerd als gelezen")
        }
    }
}

In dit voorbeeld: StateFlow slaat de meldingenlijst op (toestand — blijft behouden bij draaien), SharedFlow emitteert toast-berichten (eenmalige gebeurtenissen — worden niet herhaald bij draaien). Combinatie van twee Flow-typen — het door Google aanbevolen patroon voor ViewModel sinds 2022.

Veelgestelde vragen

Kan SharedFlow een gebeurtenis verliezen?

Ja, als de buffer vol is en onBufferOverflow = DROP_OLDEST of DROP_LATEST. SharedFlow garandeert niet de levering van elke gebeurtenis — dit is geen berichtenwachtrij (zoals Channel). Als gegarandeerde levering van alle gebeurtenissen nodig is, gebruik dan Channel met een niet-overlopen buffer (UNLIMITED) of BroadcastChannel (deprecated). Voor UI-gebeurtenissen is verlies van verouderde gebeurtenissen (bijv. oude navigatie) — verwacht gedrag, geen bug.

Hoe verschilt SharedFlow van Channel?

Channel — een FIFO-wachtrij waarin elke gebeurtenis aan precies één abonnee wordt geleverd (punt-tot-punt). SharedFlow — uitzending: elke gebeurtenis wordt aan ALLE actieve abonnees geleverd. SharedFlow lijkt meer op BroadcastChannel (dat deprecated is) en is geschikt voor „een-op-veel“-scenario's. Channel — voor „een-op-een“ (thread pools, pipeline). Volgens JetBrains-aanbeveling vervangt SharedFlow BroadcastChannel voor alle nieuwe projecten.

Hoe maak ik SharedFlow thread-safe?

SharedFlow is al thread-safe — emit() en collect() zijn correct gesynchroniseerd. Meerdere threads kunnen emit() aanroepen zonder blokkeringen en alle actieve abonnees ontvangen gebeurtenissen in de juiste volgorde. tryEmit() is niet-blokkerend — retourneert false als de buffer vol is. Gebruik voor systemen met hoge belasting tryEmit() met DROP_OLDEST — dit voorkomt blokkering van threads.

Waarom wordt SharedFlow niet gebruikt voor toestand?

SharedFlow zonder replay=1 slaat de laatste waarde niet op — bij het draaien van het scherm ontvangt de nieuwe abonnee de huidige toestand niet, blijft de UI leeg. Met replay=1 gedraagt SharedFlow zich als StateFlow, maar verliest het de optimalisatie van vergelijking via equals(), wat onnodige meldingen veroorzaakt bij het opnieuw verzenden van dezelfde waarde. StateFlow — de juiste keuze voor toestand; SharedFlow — voor gebeurtenissen.

Hoe test ik SharedFlow?

Gebruik voor het testen van SharedFlow Turbine — de Kotlin-bibliotheek voor het testen van Flow. Turbine stelt u in staat elke emissie afzonderlijk te controleren met time-outs en voltooiingscontrole. Voorbeeld: viewModel.event.test { assertEquals(UiEvent.ShowSnackbar("OK"), awaitItem()) }. U kunt ook .toList() gebruiken in runTest met opgave van het aantal verwachte gebeurtenissen.

Samenvatting

  • SharedFlow — hete reactieve stroom voor eenmalige gebeurtenissen, niet gebonden aan de laatste toestand.
  • SharedFlow vs StateFlow: SharedFlow — gebeurtenissen (navigatie, toasts, alerts), StateFlow — toestand (lijsten, laden, fouten).
  • MutableSharedFlow met replay=0, extraBufferCapacity=5, DROP_OLDEST — standaardconfiguratie voor UI-gebeurtenissen.
  • emit() — suspend-functie voor blokkerend verzenden; tryEmit() — non-suspend met Boolean-resultaat.
  • Het UiEvent-patroon met sealed class — aanbevolen manier om eenmalige gebeurtenissen van ViewModel naar View te sturen.
  • SharedFlow garandeert levering aan elke abonnee, maar garandeert niet levering van elke gebeurtenis bij bufferoverloop.
  • Combinatie van SharedFlow + StateFlow in één ViewModel — optimaal patroon voor architectuur die toestand en gebeurtenissen scheidt.

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook