FlowFlow e SharedFlow

StateFlow e SharedFlow sono API Flow che consentono ai flussi di emettere in modo ottimale aggiornamenti dello stato ed emettere valori a più consumatori.

StateFlow

StateFlow è un flusso osservabile che gestisce lo stato ed emette gli aggiornamenti dello stato corrente e del nuovo stato ai relativi raccoglitori. Il valore dello stato corrente può essere letto anche tramite la relativa proprietà value. Per aggiornare lo stato e inviarlo al flusso, assegna un nuovo valore alla proprietà value della classe MutableStateFlow.

In Android, StateFlow è ideale per le classi che devono mantenere uno stato mutabile osservabile.

Seguendo gli esempi di Kotlin Flows, un StateFlow può essere esposto dal LatestNewsViewModel in modo che il View possa ascoltare gli aggiornamenti dello stato dell'interfaccia utente e, in modo intrinseco, mantenere lo stato della schermata anche dopo le modifiche alla configurazione.

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 View 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()
}

La classe responsabile dell'aggiornamento di un MutableStateFlow è il produttore e tutte le classi che raccolgono dati dal StateFlow sono i consumatori. A differenza di un flusso freddo creato utilizzando il generatore flow, un StateFlow è caldo: la raccolta dal flusso non attiva alcun codice del produttore. Un elemento StateFlow è sempre attivo e in memoria e diventa idoneo per la garbage collection solo se non esistono altri riferimenti da una radice di garbage collection.

Quando un nuovo consumatore inizia a raccogliere dati dal flusso, riceve l'ultimo stato dello stream e gli eventuali stati successivi. Puoi trovare questo comportamento in altre classi osservabili come LiveData.

View ascolta StateFlow come per qualsiasi altro flusso:

class LatestNewsActivity : AppCompatActivity() {
    private val latestNewsViewModel = // 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)
                    }
                }
            }
        }
    }
}

Per convertire qualsiasi flusso in un StateFlow, utilizza l'operatore intermedio stateIn.

StateFlow, Flow e LiveData

StateFlow e LiveData hanno alcune similitudini. Entrambi sono classi di contenitori di dati osservabili e entrambi seguono un pattern simile se utilizzati nell'architettura dell'app.

Tieni presente, tuttavia, che StateFlow e LiveData si comportano in modo diverso:

  • StateFlow richiede che venga passato uno stato iniziale al costruttore, mentre LiveData no.
  • LiveData.observe() annulla automaticamente la registrazione del consumatore quando la visualizzazione passa allo stato STOPPED, mentre la raccolta da un StateFlow o da qualsiasi altro flusso non viene interrotta automaticamente. Per ottenere lo stesso comportamento, devi raccogliere il flusso da un blocco Lifecycle.repeatOnLifecycle.

Riscaldamento dei flussi freddi utilizzando shareIn

StateFlow è un flusso hot: rimane in memoria finché il flusso viene raccolto o finché esistono altri riferimenti ad esso da un albero di raccolta del cestino. Puoi trasformare i flussi freddi in caldi utilizzando l'operatore shareIn.

Utilizzando callbackFlow creato nei flussi Kotlin come esempio, anziché chiedere a ogni raccoglitore di creare un nuovo flusso, puoi condividere i dati recuperati da Firestore tra i raccoglitori utilizzando shareIn. Devi passare quanto segue:

  • Un CoroutineScope utilizzato per condividere il flusso. Questo ambito deve essere attivo più a lungo di qualsiasi consumatore per mantenere attivo il flusso condiviso per tutto il tempo necessario.
  • Il numero di elementi da riprodurre per ogni nuovo raccoglitore.
  • Il criterio di comportamento iniziale.
class NewsRemoteDataSource(...,
    private val externalScope: CoroutineScope,
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        ...
    }.shareIn(
        externalScope,
        replay = 1,
        started = SharingStarted.WhileSubscribed()
    )
}

In questo esempio, il flusso latestNews riproduce l'ultimo elemento emesso in un nuovo collettore e rimane attivo finché latestNews è attivo e ci sono collettori attivi.externalScope Il criterio di avvio SharingStarted.WhileSubscribed() mantiene attivo il produttore in upstream finché sono presenti abbonati attivi. Sono disponibili altri criteri di inizio, ad esempio SharingStarted.Eagerly per avviare immediatamente il produttore o SharingStarted.Lazily per avviare la condivisione dopo l'arrivo del primo iscritto e mantenere attivo il flusso per sempre.

SharedFlow

La funzione shareIn restituisce un SharedFlow, un flusso caldo che emette valori a tutti i consumatori che li raccolgono. Un SharedFlow è una generalizzazione di StateFlow altamente configurabile.

Puoi creare un SharedFlow senza utilizzare shareIn. Ad esempio, potresti utilizzare SharedFlow per inviare segni di graduazione al resto dell'app in modo che tutti i contenuti vengano aggiornati periodicamente contemporaneamente. Oltre a recuperare le ultime notizie, potresti anche voler aggiornare la sezione delle informazioni sugli utenti con la raccolta degli argomenti preferiti. Nel seguente snippet di codice, un TickHandler espone un SharedFlow in modo che le altre classi sappiano quando aggiornarne i contenuti. Come per StateFlow, utilizza una proprietà di supporto di tipo MutableSharedFlow in una classe per inviare elementi al flusso:

// 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<Event<String>> = _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() { ... }
    ...
}

Puoi personalizzare il comportamento di SharedFlow nei seguenti modi:

  • replay ti consente di inviare nuovamente una serie di valori emessi in precedenza per i nuovi abbonati.
  • onBufferOverflow ti consente di specificare un criterio per quando il buffer è pieno di elementi da inviare. Il valore predefinito è BufferOverflow.SUSPEND, quindi il chiamante viene sospeso. Le altre opzioni sono DROP_LATEST o DROP_OLDEST.

MutableSharedFlow ha anche una proprietà subscriptionCount che contiene il numero di collector attivi, in modo da poter ottimizzare la logica aziendale di conseguenza. MutableSharedFlow contiene anche una funzione resetReplayCache se non vuoi riprodurre le ultime informazioni inviate al flusso.

Risorse aggiuntive per i flussi