SharedFlow und StateFlow
Dies ist ein übersetztes Kapitel aus dem Buc Kotlin Coroutines. Wenn Sie mir helfen möchten, die Übersetzung zu verbessern, finden Sie die Quellen auf GitHub.
Flow ist typischerweise kalt, daher werden seine Werte bei Bedarf berechnet. Es gibt jedoch Fälle, in denen mehrere Empfänger an einer einzigen Änderungsquelle abonniert sein sollten. Hier kommt SharedFlow ins Spiel, welches konzeptuell einer Mailingliste ähnelt. Wir kennen auch StateFlow, das einem beobachtbaren Wert ähnelt. Wir erklären beide Schritt für Schritt.
SharedFlow
Beginnen wir mit MutableSharedFlow
, das einem Broadcast-Kanal ähnelt: Jeder kann Nachrichten senden (ausstrahlen), die von allen Coroutinen empfangen werden, die zuhören (sammeln).
Das obige Programm endet nie, da
coroutineScope
auf die mitlaunch
gestartetencoroutines
wartet, die ständig anMutableSharedFlow
hängen. Offensichtlich kannMutableSharedFlow
nicht geschlossen werden, daher muss der gesamte Bereich abgebrochen werden, um dieses Problem zu beheben.
MutableSharedFlow
kann weiterhin Nachrichten senden. Wenn der replay
Parameter festgelegt wird (standardmäßig ist er auf 0 gesetzt), werden die festgelegten letzten Werte behalten. Wenn eine coroutine
nun anfängt zu beobachten, erhält sie zuerst diese Werte. Der Cache kann mit resetReplayCache
zurückgesetzt werden.
MutableSharedFlow
ist konzeptionell ähnlich wie RxJava Subjects. Wenn derreplay
Parameter auf 0 gesetzt ist, ähnelt es einemPublishSubject
. Wennreplay
1 ist, ähnelt es einemBehaviorSubject
. Wennreplay
Int.MAX_VALUE
ist, ähnelt es einemReplaySubject
.
In Kotlin bevorzugen wir eine Unterscheidung zwischen Schnittstellen, die nur zum Empfangen verwendet werden, und solchen, die zur Modifikation verwendet werden. Zum Beispiel haben wir bereits die Unterscheidung zwischen SendChannel
, ReceiveChannel
und einfach Channel
gesehen. Das gleiche Prinzip gilt hier. MutableSharedFlow
erbt sowohl von SharedFlow
als auch von FlowCollector
. Ersteres erbt von Flow
und dient zur Beobachtung, während FlowCollector
zum Senden von Werten verwendet wird.
Diese Schnittstellen werden oft nur dazu verwendet, Funktionen auszugeben, oder nur zum Erfassen.
Hier ist ein Beispiel für die typische Verwendung auf Android:
shareIn
Flow wird oft dazu verwendet, Änderungen nachzuverfolgen, wie Benutzeraktionen, Datenbankmodifikationen oder neue Nachrichten. Wir kennen bereits die verschiedenen Wege, auf denen diese Ereignisse verarbeitet und gehandhabt werden können. Wir wissen, wie man mehrere Flows zu einem vereint. Aber was ist, wenn mehrere Klassen an diesen Änderungen interessiert sind und wir möchten einen Flow in mehrere Flows umwandeln? Die Lösung ist SharedFlow
, und der einfachste Weg, einen Flow
in einen SharedFlow
zu verwandeln, ist die Nutzung der Funktion shareIn
.
Die shareIn
Funktion erstellt einen SharedFlow
und sendet Elemente aus ihrem Flow
. Da wir eine Coroutine starten müssen, um Elemente auf dem Flow zu sammeln, erwartet shareIn
einen Coroutine-Scope als erstes Argument. Das dritte Argument ist replay
, welches standardmäßig 0 ist. Das zweite Argument ist interessant: started
bestimmt, wann das Warten auf Werte beginnen soll, abhängig von der Anzahl der Abonnenten. Die folgenden Optionen werden unterstützt:
SharingStarted.Eagerly
- Beginnt sofort mit dem Warten auf Werte und sendet sie an einen Flow. Beachten Sie, dass Sie einige Werte verlieren könnten, wenn Sie einen begrenztenreplay
Wert haben und Ihre Werte veröffentlicht werden, bevor Sie zu abonnieren beginnen (wenn Ihr replay 0 ist, werden Sie alle solche Werte verlieren).
SharingStarted.Lazily
- startet den Abruf, wenn der erste Abonnent erscheint. Dies garantiert, dass dieser erste Abonnent alle ausgesendeten Werte erhält, während nachfolgenden Abonnenten nur garantiert wird, die neuesten Wiedergabewerte zu erhalten. Der Datenstrom bleibt aktiv, auch wenn alle Abonnenten verschwinden, aber ohne Abonnenten werden nur die neuesten Wiedergabewerte zwischengespeichert.
WhileSubscribed()
- beginnt den Flow zu überwachen, wenn der erste Abonnent erscheint; es stoppt, wenn der letzte Abonnent verschwindet. Wenn ein neuer Abonnent erscheint, wenn unserSharedFlow
gestoppt ist, wird es erneut starten.WhileSubscribed
hat zusätzliche optionale Konfigurationsparameter:stopTimeoutMillis
(wie lange zu warten, nachdem der letzte Abonnent verschwunden ist, standardmäßig 0) undreplayExpirationMillis
(wie lange die Wiedergabe nach dem Stoppen beibehalten wird, standardmäßigLong.MAX_VALUE
).
- Es ist auch möglich, eine benutzerdefinierte Strategie zu definieren, indem das
SharingStarted
Interface implementiert wird.
Die Verwendung von shareIn
ist sehr praktisch, wenn mehrere Dienste an den gleichen Änderungen interessiert sind. Nehmen wir an, Sie müssen beobachten, wie gespeicherte Orte sich im Laufe der Zeit ändern. So könnte ein DTO (Data Transfer Object - Datenübertragungsobjekt) auf Android mit der Room-Bibliothek implementiert werden:
Das Problem ist, dass es nicht optimal ist, wenn mehrere Dienste auf diese Standorte angewiesen sind und jeder davon die Datenbank separat überwachen muss. Stattdessen könnten wir einen Dienst erstellen, der auf diese Änderungen reagiert und sie in SharedFlow
einfließt. Das ist der Punkt, an dem wir shareIn
nutzen werden. Aber wie sollten wir es konfigurieren? Diese Entscheidung müssen Sie selbst treffen. Möchten Sie, dass Ihre Abonnenten sofort die aktuellste Liste der Standorte erhalten? Wenn ja, setzen Sie replay
auf 1. Wenn Sie nur auf Änderungen reagieren möchten, setzen Sie es auf 0. Und was ist mit started
? WhileSubscribed()
scheint das beste Einsatzszenario für diesen Fall zu sein.
Achtung! Erstellen Sie nicht bei jedem Aufruf einen neuen SharedFlow. Erstellen Sie einen und speichern Sie ihn in einer Variable.
StateFlow
StateFlow ist eine Erweiterung des SharedFlow-Konzepts. Es verhält sich ähnlich wie SharedFlow, wenn der Parameter replay
auf 1 gesetzt ist. Es speichert stets einen Wert, auf den über das Attribut value
zugegriffen werden kann.
Bitte beachten Sie, wie die
value
Property inMutableStateFlow
überschrieben wird. In Kotlin kann eineopen val
Property mit einervar
Property überschrieben werden.val
erlaubt nur das Abrufen eines Wertes (getter), währendvar
auch das Setzen eines neuen Wertes (setter) unterstützt.
Der Anfangswert muss an den Konstruktor übergeben werden. Wir greifen auf den Wert zu und setzen ihn mit der value
Property. Wie Sie sehen können, ist MutableStateFlow
wie ein beobachtbarer Behälter für einen Wert.
Auf Android wird StateFlow als moderne Alternative zu LiveData verwendet. Erstens hat es volle Unterstützung für Coroutinen. Zweitens hat es einen Anfangswert, so dass es nicht notwendig ist, dass es null sein kann. Daher wird StateFlow oft in ViewModels verwendet, um deren Zustand darzustellen. Dieser Zustand wird beobachtet, und eine View wird entsprechend dieser Basis angezeigt und aktualisiert.
Seien Sie vorsichtig, dass StateFlow 'verschmolzen' ist, sodass langsamere Beobachter eventuell nicht alle 'Zwischenzustände' empfangen könnten. Um alle 'events' zu empfangen, sollten Sie 'SharedFlow' benutzen.
Dieses Verhalten ist so konzipiert. StateFlow repräsentiert den aktuellen Zustand und wir könnten davon ausgehen, dass niemand an einem veralteten Zustand interessiert ist.
stateIn
stateIn
ist eine Funktion, die Flow<T>
in StateFlow<T>
umwandelt. Sie kann nur innerhalb eines Scopes aufgerufen werden, aber es ist eine suspendierende Funktion. Bedenken Sie, dass ein StateFlow immer einen Wert haben muss; wenn Sie also keinen Wert angeben, müssen Sie warten, bis der erste Wert berechnet ist.
Die zweite Variante von stateIn
ist nicht suspendierend, aber sie erfordert einen Initialwert und einen gestartet
Modus. Dieser Modus hat die gleichen Optionen wie shareIn
(wie zuvor erläutert).
Wir verwenden typischerweise stateIn
, wenn wir einen Wert aus einer Änderungsquelle beobachten möchten. In diesem Prozess können diese Änderungen verarbeitet werden und schließlich können sie durch unsere Sichten beobachtet werden.
Zusammenfassung
In diesem Kapitel haben wir über SharedFlow
und StateFlow
gelernt, die beide besonders wichtig für Android-Entwickler sind, da sie häufig als Teil des MVVM-Musters verwendet werden. Denken Sie an sie und erwägen Sie, sie zu verwenden, insbesondere wenn Sie View-Modelle in der Android-Entwicklung verwenden.