StateFlow und SharedFlow

StateFlow und SharedFlow sind Flow APIs, mit denen Flows Statusaktualisierungen optimal und Werte an mehrere Consumer senden können.

StateFlow

StateFlow ist ein beobachtbarer Flow, der den aktuellen und neuen Status an seine Collector ausgibt. Der aktuelle Statuswert kann auch über die Property value gelesen werden. Wenn Sie den Status aktualisieren und an den Ablauf senden möchten, weisen Sie der value-Property der Klasse MutableStateFlow einen neuen Wert zu.

In Android ist StateFlow eine gute Wahl für Klassen, die einen beobachtbaren, veränderlichen Zustand beibehalten müssen.

Gemäß den Beispielen aus Kotlin-Flows kann ein StateFlow über das LatestNewsViewModel bereitgestellt werden, sodass das View auf Aktualisierungen des UI-Status warten kann und der Bildschirmstatus Konfigurationsänderungen übersteht.

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    // Backing property to avoid state updates from other classes
    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    // The UI collects from this StateFlow to get its state updates
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                // Update UI with the latest favorite news
                // Writes to the value property of MutableStateFlow,
                // adding a new element to the flow and updating all
                // of its collectors
                .collect { favoriteNews ->
                    _uiState.value = LatestNewsUiState.Success(favoriteNews)
                }
        }
    }
}

// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
    data class Success(val news: List<ArticleHeadline>) : LatestNewsUiState()
    data class Error(val exception: Throwable) : LatestNewsUiState()
}

Die Klasse, die für die Aktualisierung eines MutableStateFlow verantwortlich ist, ist der Producer. Alle Klassen, die Daten aus dem StateFlow abrufen, sind die Consumer. Im Gegensatz zu einem kalten Flow, der mit dem flow-Builder erstellt wurde, ist ein StateFlow heiß: Beim Erfassen aus dem Flow wird kein Erstellercode ausgelöst. Ein StateFlow ist immer aktiv und im Arbeitsspeicher. Es kommt erst für die Garbage Collection infrage, wenn keine anderen Referenzen darauf von einem Garbage Collection-Root vorhanden sind.

Wenn ein neuer Consumer mit dem Erfassen von Daten aus dem Stream beginnt, empfängt er den letzten Status im Stream und alle nachfolgenden Status. Dieses Verhalten ist auch in anderen beobachtbaren Klassen wie LiveData zu finden.

Die View wartet wie bei jedem anderen Ablauf auf StateFlow:

class LatestNewsActivity : ComponentActivity() {
    private val latestNewsViewModel: LatestNewsViewModel = TODO() // getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        // Start a coroutine in the lifecycle scope
        lifecycleScope.launch {
            // repeatOnLifecycle launches the block in a new coroutine every time the
            // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Trigger the flow and start listening for values.
                // Note that this happens when lifecycle is STARTED and stops
                // collecting when the lifecycle is STOPPED
                latestNewsViewModel.uiState.collect { uiState ->
                    // New value received
                    when (uiState) {
                        is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                        is LatestNewsUiState.Error -> showError(uiState.exception)
                    }
                }
            }
        }
    }
}

Wenn Sie einen beliebigen Ablauf in ein StateFlow konvertieren möchten, verwenden Sie den Zwischenoperator stateIn.

StateFlow, Flow und LiveData

StateFlow und LiveData haben Ähnlichkeiten. Beide sind Klassen für beobachtbare Daten und folgen einem ähnlichen Muster, wenn sie in Ihrer App-Architektur verwendet werden.

StateFlow und LiveData verhalten sich jedoch unterschiedlich:

  • Für StateFlow muss ein Anfangszustand an den Konstruktor übergeben werden, für LiveData nicht.
  • Bei LiveData.observe() wird die Registrierung des Nutzers automatisch aufgehoben, wenn der View in den Status STOPPED wechselt. Bei der Erfassung über einen StateFlow- oder einen anderen Ablauf wird die Erfassung nicht automatisch beendet. Um dasselbe Verhalten zu erzielen, müssen Sie den Flow aus einem Lifecycle.repeatOnLifecycle-Block erfassen.

Kalte Flows mit shareIn heiß machen

StateFlow ist ein aktiver Ablauf. Er bleibt im Arbeitsspeicher, solange er erfasst wird oder solange andere Verweise darauf von einem Garbage Collection-Root aus vorhanden sind. Mit dem Operator shareIn können Sie kalte Flows in heiße Flows umwandeln.

Anhand des in Kotlin-Flows erstellten callbackFlow können Sie die aus Firestore abgerufenen Daten zwischen Collectors teilen, indem Sie shareIn verwenden. So muss nicht für jeden Collector ein neuer Flow erstellt werden. Sie müssen Folgendes übergeben:

  • Eine CoroutineScope, die zum Freigeben des Ablaufs verwendet wird. Dieser Bereich sollte länger als jeder Consumer aktiv sein, damit der gemeinsame Ablauf so lange wie nötig aktiv bleibt.
  • Die Anzahl der Elemente, die für jeden neuen Collector wiedergegeben werden sollen.
  • Die Richtlinie für das Startverhalten.

class NewsRemoteDataSource(
    private val externalScope: CoroutineScope
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        // ...
    }.shareIn(
        externalScope,
        replay = 1,
        started = SharingStarted.WhileSubscribed()
    )
}

In diesem Beispiel gibt der latestNews-Ablauf das zuletzt ausgegebene Element an einen neuen Collector aus und bleibt aktiv, solange externalScope aktiv ist und es aktive Collectors gibt. Die SharingStarted.WhileSubscribed()-Startrichtlinie hält den Upstream-Produzenten aktiv, solange es aktive Abonnenten gibt. Es sind auch andere Startrichtlinien verfügbar, z. B. SharingStarted.Eagerly, um den Producer sofort zu starten, oder SharingStarted.Lazily, um die Freigabe nach dem ersten Abonnenten zu starten und den Flow für immer aktiv zu halten.

SharedFlow

Die Funktion shareIn gibt einen SharedFlow zurück, einen Hot Flow, der Werte an alle Consumer ausgibt, die Daten daraus abrufen. Eine SharedFlow ist eine hochgradig konfigurierbare Verallgemeinerung von StateFlow.

Sie können ein SharedFlow erstellen, ohne shareIn zu verwenden. Sie können beispielsweise einen SharedFlow verwenden, um Ticks an den Rest der App zu senden, damit alle Inhalte regelmäßig gleichzeitig aktualisiert werden. Neben dem Abrufen der neuesten Nachrichten möchten Sie vielleicht auch den Bereich mit Nutzerinformationen mit der Sammlung der Lieblingsthemen aktualisieren. Im folgenden Code-Snippet macht ein TickHandler ein SharedFlow verfügbar, damit andere Klassen wissen, wann sie ihre Inhalte aktualisieren müssen. Wie bei StateFlow verwenden Sie eine Sicherungsproperty vom Typ MutableSharedFlow in einer Klasse, um Elemente an den Flow zu senden:

// Class that centralizes when the content of the app needs to be refreshed
class TickHandler(
    private val externalScope: CoroutineScope,
    private val tickIntervalMs: Long = 5000
) {
    // Backing property to avoid flow emissions from other classes
    private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
    val tickFlow: SharedFlow<Unit> = _tickFlow

    init {
        externalScope.launch {
            while (true) {
                _tickFlow.emit(Unit)
                delay(tickIntervalMs)
            }
        }
    }
}

class NewsRepository(
    // ...
    private val tickHandler: TickHandler,
    private val externalScope: CoroutineScope
) {
    init {
        externalScope.launch {
            // Listen for tick updates
            tickHandler.tickFlow.collect {
                refreshLatestNews()
            }
        }
    }

    suspend fun refreshLatestNews() { /* ... */ }
    // ...
}

Sie haben folgende Möglichkeiten, das Verhalten von SharedFlow anzupassen:

  • Mit replay können Sie eine Reihe von zuvor gesendeten Werten für neue Abonnenten noch einmal senden.
  • Mit onBufferOverflow können Sie eine Richtlinie dafür angeben, wann der Puffer mit zu sendenden Elementen gefüllt ist. Der Standardwert ist BufferOverflow.SUSPEND. Das bedeutet, dass der Anrufer aufgelegt wird. Weitere Optionen sind DROP_LATEST oder DROP_OLDEST.

MutableSharedFlow hat auch eine subscriptionCount-Property, die die Anzahl der aktiven Collectors enthält, damit Sie Ihre Geschäftslogik entsprechend optimieren können. MutableSharedFlow enthält auch die Funktion resetReplayCache, wenn Sie die letzten an den Flow gesendeten Informationen nicht noch einmal wiedergeben möchten.

Zusätzliche Ressourcen für den Ablauf