SharedFlow: Was es ist, SharedFlow vs StateFlow in Android

Autor: IT Sectr Veröffentlicht: 2026-02-20 Lesezeit: 9 Min.

SharedFlow ist ein heißer reaktiver Flow aus der Kotlin Coroutines Bibliothek, optimiert für einmalige Ereignisse (one-shot events), die sich bei Bildschirmdrehung oder Neuerstellung des Abonnenten nicht wiederholen sollen. Wir zeigen, wie sich SharedFlow von StateFlow unterscheidet: Im Gegensatz zu StateFlow speichert SharedFlow den letzten Wert nicht für neue Abonnenten und unterstützt die Konfiguration von replay, extraBufferCapacity und onBufferOverflow. Laut Google (Android Developers, 2025) ist SharedFlow die empfohlene Lösung für Navigationsbefehle, Snackbar-Nachrichten und andere Ereignisse, die genau einmal verarbeitet werden sollen.

Wichtige Punkte

  • SharedFlow — heißer Flow für einmalige Ereignisse: neue Abonnenten erhalten ohne replay-Konfiguration keine vorherigen Werte.
  • MutableSharedFlow — veränderbare Version mit Methoden emit() und tryEmit() zum Senden von Ereignissen.
  • SharedFlow vs StateFlow: SharedFlow konflationiert Werte nicht (kann mehrere puffern), benötigt keinen Anfangswert, geeignet für einmalige Ereignisse.
  • replay — Anzahl der letzten Ereignisse, die neuen Abonnenten wiedergegeben werden (Standard 0).
  • extraBufferCapacity — zusätzlicher Puffer für Ereignisse über replay hinaus, der das Blockieren von emit() verhindert.

Was ist SharedFlow in Kotlin?

SharedFlow ist ein heißer Flow (hot flow) aus der Bibliothek kotlinx.coroutines.flow, der im Gegensatz zu StateFlow nicht an einen einzelnen Zustand gebunden ist und eine beliebige Anzahl von Ereignissen an beliebige Abonnenten senden kann. SharedFlow ist der Basistyp für StateFlow — StateFlow wird tatsächlich über SharedFlow mit replay = 1 implementiert.

Das Hauptmerkmal von SharedFlow ist, dass es den letzten Wert nicht speichern muss. Standardmäßig (replay = 0) erhält ein neuer Abonnent nichts, bis ein neues Ereignis gesendet wird. Dies macht SharedFlow ideal für Szenarien, in denen ein Ereignis genau einmal verarbeitet werden soll: Navigation, Snackbar, Systembenachrichtigungen, QR-Code-Scanergebnisse.

SharedFlow wurde in kotlinx.coroutines 1.4.0 (November 2020) zusammen mit StateFlow stabilisiert. Laut der Kotlin Coroutines Dokumentation (2025) verwendet SharedFlow feinkörnige Sperren für die Synchronisierung von Abonnenten und bietet lineare Skalierbarkeit auf über 1000 gleichzeitige Abonnenten ohne Leistungseinbußen, bestätigt durch JetBrains-Tests.

SharedFlow vs StateFlow: Wann was verwenden

Die Wahl zwischen SharedFlow und StateFlow hängt von der Semantik der übertragenen Daten ab: Zustand (StateFlow) oder Ereignis (SharedFlow). Nachfolgend finden Sie klare Kriterien mit Beispielen.

KriteriumSharedFlowStateFlow
SemantikEinmalige Ereignisse (Navigation, Toast, Alert)UI-Zustand (Liste, Laden, Fehler)
AnfangswertNicht erforderlichErforderlich
Wiederholung bei AbonnementNur wenn replay > 0Immer letzter Wert
KonflationNein — Ereignisse gehen nicht verloren (wenn Puffer nicht voll)Ja — speichert nur den neuesten
PufferungKonfigurierbar über replay + extraBufferCapacityNur 1 (replay=1 fest)
VerwendungnavigationEvent, showSnackbar, openDialogitems, isLoading, uiState

Die einfachste Regel: Wenn die Daten bei Bildschirmdrehung angezeigt werden sollen — ist es Zustand (StateFlow). Wenn sich das Ereignis bei Bildschirmdrehung nicht wiederholen soll — ist es ein einmaliges Ereignis (SharedFlow). Zum Beispiel ein Toast mit einer Fehlermeldung — SharedFlow: bei Drehung soll der Toast nicht erneut erscheinen. Eine Produktliste — StateFlow: bei Drehung soll die Liste auf dem Bildschirm bleiben.

Bei IT Sectr verwenden wir SharedFlow für: Navigationsbefehle (Bildschirmübergang, Deep-Link-Öffnung), UI-Ereignisse (Snackbar, AlertDialog), Systembenachrichtigungen (Hintergrunddatenaktualisierungen, Zahlungsergebnis), Analyseereignisse (Protokollierung, Tracking).

MutableSharedFlow: emit, tryEmit und Pufferung

MutableSharedFlow ist die veränderbare Version von SharedFlow mit den Methoden emit() (suspend) und tryEmit() (nicht suspend) zum Senden von Ereignissen. emit() wird angehalten, wenn der Puffer voll ist und onBufferOverflow = SUSPEND. tryEmit() gibt einen Boolean zurück, der angibt, ob das Ereignis erfolgreich zum Puffer hinzugefügt wurde.

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
}

Die Konstruktorparameter sind von entscheidender Bedeutung: replay = 0 stellt sicher, dass das Ereignis für einen neuen Abonnenten nicht wiederholt wird; extraBufferCapacity = 10 bietet einen Puffer für schnelles Senden von Ereignissen, bevor die UI abonniert; DROP_OLDEST ist die Überlaufstrategie: alte Ereignisse werden verworfen, neue werden beibehalten. Laut Kotlin Coroutines Performance (JetBrains, 2024) verarbeitet SharedFlow mit extraBufferCapacity = 64 über 100.000 Ereignisse pro Sekunde ohne Verluste.

SharedFlow für einmalige Ereignisse: das Event-Muster

Event-Muster (oder UiEvent) ist die von Google empfohlene Methode, um einmalige Ereignisse von ViewModel an View zu übergeben. Im Gegensatz zum Zustand (StateFlow) sollte ein Ereignis genau einmal verarbeitet werden und bei Bildschirmdrehung nicht wiederholt werden. SharedFlow mit replay = 0 ist ideal für diese Aufgabe.

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 ?: "Layoutfehler"))
            }
        }
    }
}

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

In der View (Activity/Fragment): Das Abonnement von Ereignissen sollte in lifecycleScope mit repeatOnLifecycle(STATE.STARTED) erfolgen. Bei jedem Eintritt in STARTED wird das Abonnement neu erstellt, aber das Ereignis wiederholt sich nicht, da SharedFlow mit replay=0 es bereits freigegeben hat. Dadurch wird sichergestellt, dass die Navigation zum Bestellverfolgungsbildschirm nur einmal erfolgt, nicht bei jeder Drehung.

SharedFlow-Parameter: replay, extraBufferCapacity, onBufferOverflow

Der Konstruktor von MutableSharedFlow akzeptiert drei Parameter, die das Pufferverhalten bestimmen. Eine falsche Konfiguration kann zu Ereignisverlust oder Blockierung von emit() führen.

ParameterTypStandardBeschreibung
replayInt0Anzahl der letzten Ereignisse, die einem neuen Abonnenten wiedergegeben werden. 0 = nicht wiedergeben, 1 = wie StateFlow
extraBufferCapacityInt0Zusätzlicher Puffer über replay hinaus. Ereignisse werden in einem ringförmigen Puffer gespeichert. 64 ist die empfohlene Grenze für die meisten Szenarien
onBufferOverflowBufferOverflowSUSPENDStrategie bei vollem Puffer: SUSPEND, DROP_OLDEST, DROP_LATEST
kotlin
// Konfigurationen für verschiedene Szenarien:

// 1. Einmalige UI-Ereignisse (Navigation, Toasts)
val uiEvents = MutableSharedFlow<UiEvent>(
    replay = 0,
    extraBufferCapacity = 5,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

// 2. Replay-Flow zur Zustandssynchronisation (wie StateFlow)
val stateLike = MutableSharedFlow<AppState>(
    replay = 1,
    extraBufferCapacity = 0
)

// 3. Hochfrequentes Senden von Ereignissen (Analytics, Logs)
val analytics = MutableSharedFlow<AnalyticsEvent>(
    replay = 0,
    extraBufferCapacity = 100,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

Wichtig: extraBufferCapacity + replay = Gesamtpuffergröße. Wenn emit() schneller aufgerufen wird, als der Abonnent Ereignisse verarbeitet, füllt sich der Puffer und onBufferOverflow wird ausgelöst. Für UI-Ereignisse ist DROP_OLDEST eine sichere Strategie: alte Ereignisse (nicht mehr relevante Navigationen) werden zugunsten neuer verworfen. Für Finanztransaktionen verwenden Sie SUSPEND — dies garantiert, dass kein Ereignis verloren geht, auf Kosten der Blockierung des Senders.

Code-Beispiele: SharedFlow in Kotlin

Beispiel 1: SharedFlow für Navigation mit Jetpack Navigation

Navigationsbefehle sind ein klassischer Anwendungsfall für SharedFlow. Ein Fragment abonniert Ereignisse und führt die Navigation durch. Bei Bildschirmdrehung wiederholt sich der Befehl nicht.

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
}

// Im 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()
            }
        }
    }
}

Beispiel 2: SharedFlow mit Room und Flow-Operatoren

Ein komplexes Szenario: SharedFlow für Hintergrundereignisbenachrichtigungen kombiniert mit StateFlow für die 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("Neue Benachrichtigung: ${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 Benachrichtigungen als gelesen markiert")
        }
    }
}

In diesem Beispiel: StateFlow speichert die Benachrichtigungsliste (Zustand — bleibt bei Drehung erhalten), SharedFlow sendet Toast-Nachrichten (einmalige Ereignisse — werden bei Drehung nicht wiederholt). Die Kombination zweier Flow-Typen ist das von Google ab 2022 empfohlene Muster für ViewModel.

Häufig gestellte Fragen

Kann SharedFlow ein Ereignis verlieren?

Ja, wenn der Puffer voll ist und onBufferOverflow = DROP_OLDEST oder DROP_LATEST. SharedFlow garantiert nicht die Zustellung jedes Ereignisses — es ist keine Nachrichtenwarteschlange (wie Channel). Wenn Sie eine garantierte Zustellung aller Ereignisse benötigen, verwenden Sie Channel mit unbegrenztem Puffer (UNLIMITED) oder BroadcastChannel (veraltet). Bei UI-Ereignissen ist der Verlust veralteter Ereignisse (z. B. alte Navigation) erwartetes Verhalten, kein Fehler.

Wie unterscheidet sich SharedFlow von Channel?

Channel ist eine FIFO-Warteschlange, bei der jedes Ereignis genau einem Abonnenten zugestellt wird (Punkt-zu-Punkt). SharedFlow ist eine Übertragung: jedes Ereignis wird ALLEN aktiven Abonnenten zugestellt. SharedFlow ist näher an BroadcastChannel (der veraltet ist) und eignet sich für Eins-zu-Viele-Szenarien. Channel ist für Eins-zu-Eins (Thread-Pools, Pipelines). Laut JetBrains-Empfehlung ist SharedFlow der Ersatz für BroadcastChannel in allen neuen Projekten.

Wie macht man SharedFlow threadsicher?

SharedFlow ist bereits threadsicher — emit() und collect() sind ordnungsgemäß synchronisiert. Mehrere Threads können emit() ohne Sperren aufrufen, und alle aktiven Abonnenten erhalten Ereignisse in der richtigen Reihenfolge. tryEmit() ist nicht blockierend — es gibt false zurück, wenn der Puffer voll ist. Für hochbelastete Systeme verwenden Sie tryEmit() mit DROP_OLDEST — dies verhindert Thread-Blockierung.

Warum wird SharedFlow nicht für Zustand verwendet?

SharedFlow ohne replay=1 speichert den letzten Wert nicht — bei Bildschirmdrehung erhält ein neuer Abonnent den aktuellen Zustand nicht und die UI bleibt leer. Mit replay=1 verhält sich SharedFlow wie StateFlow, verliert jedoch die Optimierung des Vergleichs durch equals(), was bei erneuter Sendung desselben Werts unnötige Benachrichtigungen verursacht. StateFlow ist die richtige Wahl für Zustand; SharedFlow ist für Ereignisse.

Wie testet man SharedFlow?

Zum Testen von SharedFlow verwenden Sie Turbine — eine Kotlin-Bibliothek zum Testen von Flow. Turbine ermöglicht die Überprüfung jeder einzelnen Ausgabe mit Timeouts und Abschlussbestätigung. Beispiel: viewModel.event.test { assertEquals(UiEvent.ShowSnackbar("OK"), awaitItem()) }. Sie können auch .toList() in runTest mit Angabe der erwarteten Ereignisanzahl verwenden.

Zusammenfassung

  • SharedFlow — ein heißer reaktiver Flow für einmalige Ereignisse, die nicht an den letzten Zustand gebunden sind.
  • SharedFlow vs StateFlow: SharedFlow für Ereignisse (Navigation, Toasts, Alerts), StateFlow für Zustand (Listen, Laden, Fehler).
  • MutableSharedFlow mit replay=0, extraBufferCapacity=5, DROP_OLDEST ist die Standardkonfiguration für UI-Ereignisse.
  • emit() — suspend-Funktion für blockierendes Senden; tryEmit() — nicht-suspend mit Boolean-Ergebnis.
  • Das UiEvent mit sealed class-Muster ist die von Google empfohlene Methode, um einmalige Ereignisse von ViewModel an View zu übergeben.
  • SharedFlow garantiert die Zustellung an jeden Abonnenten, garantiert jedoch nicht die Zustellung jedes Ereignisses bei Pufferüberlauf.
  • Die Kombination von SharedFlow + StateFlow in einem einzigen ViewModel ist das optimale Muster für eine Architektur, die Zustand und Ereignisse trennt.

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch