StateFlow e SharedFlow sono API di Flow
che consentono ai flussi di emettere in modo ottimale gli aggiornamenti dello stato ed emettere valori a più
consumer.
StateFlow
StateFlow
è un flusso osservabile di tipo state-holder che emette gli aggiornamenti dello stato corrente e del nuovo stato ai relativi collector. Il valore dello stato corrente può essere letto anche tramite la
value
proprietà. Per aggiornare lo stato e inviarlo al flusso, assegna un nuovo valore a
la value proprietà della
MutableStateFlow classe.
In Android, StateFlow è ideale per le classi che devono mantenere uno stato modificabile osservabile.
Seguendo gli esempi dei flussi Kotlin, un StateFlow
può essere esposto da LatestNewsViewModel in modo che la View possa
ascoltare gli aggiornamenti dello stato dell'UI e, per sua natura, fare in modo che lo stato dello schermo sopravviva alle
modifiche di 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 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() }
La classe responsabile dell'aggiornamento di un MutableStateFlow è il producer,
mentre tutte le classi che raccolgono dati da StateFlow sono i consumer. A differenza
di un flusso cold creato utilizzando il builder flow, un StateFlow è hot:
la raccolta di dati dal flusso non attiva alcun codice producer. Un StateFlow è sempre attivo e in memoria e diventa idoneo per il garbage collection solo quando non esistono altri riferimenti a esso da una radice di garbage collection.
Quando un nuovo consumer inizia a raccogliere dati dal flusso, riceve l'ultimo stato nello stream e tutti gli stati successivi. Puoi trovare questo comportamento
in altre classi osservabili come
LiveData.
La View ascolta StateFlow come qualsiasi altro flusso:
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) } } } } } }
Per convertire qualsiasi flusso in un StateFlow, utilizza l'
stateIn
operatore intermedio.
StateFlow, Flow e LiveData
StateFlow e LiveData hanno
delle somiglianze. Entrambe sono classi di tipo data holder osservabili ed entrambe seguono un pattern simile quando vengono utilizzate nell'architettura dell'app.
Tieni presente, tuttavia, che StateFlow e
LiveData si comportano in modo diverso:
StateFlowrichiede che uno stato iniziale venga passato al costruttore, mentreLiveDatano.LiveData.observe()annulla automaticamente la registrazione del consumer quando la visualizzazione passa allo statoSTOPPED, mentre la raccolta di dati da unStateFlowo da qualsiasi altro flusso non si interrompe automaticamente. Per ottenere lo stesso comportamento, devi raccogliere il flusso da un bloccoLifecycle.repeatOnLifecycle.
Rendere hot i flussi cold utilizzando shareIn
StateFlow è un flusso hot : rimane in memoria finché il flusso viene raccolto o finché esistono altri riferimenti a esso da una radice di garbage collection. Puoi rendere hot i flussi cold utilizzando l'
shareIn
operatore.
Utilizzando come
esempio il callbackFlow creato nei flussi Kotlin, anziché fare in modo che ogni collector crei un nuovo flusso, puoi condividere
i dati recuperati da Firestore tra i collector utilizzando shareIn.
Devi passare quanto segue:
- Un
CoroutineScopeutilizzato per condividere il flusso. Questo ambito deve avere una durata maggiore di quella di qualsiasi consumer per mantenere attivo il flusso condiviso per tutto il tempo necessario. - Il numero di elementi da riprodurre per ogni nuovo collector.
- Il criterio di comportamento di avvio.
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 per un nuovo collector e rimane attivo finché externalScope è attivo e sono presenti collector attivi. Il criterio di avvio SharingStarted.WhileSubscribed() mantiene attivo il producer upstream mentre sono presenti subscriber attivi. Sono disponibili altri criteri di avvio, come SharingStarted.Eagerly per avviare immediatamente il producer o SharingStarted.Lazily per avviare la condivisione dopo la visualizzazione del primo subscriber e mantenere attivo il flusso per sempre.
SharedFlow
La funzione shareIn restituisce un SharedFlow, un flusso hot che emette valori a tutti i consumer che li raccolgono. Un SharedFlow è una generalizzazione altamente configurabile di StateFlow.
Puoi creare un SharedFlow senza utilizzare shareIn. Ad esempio, puoi utilizzare un SharedFlow per inviare tick al resto dell'app in modo che tutti i contenuti vengano aggiornati periodicamente nello stesso momento. Oltre a recuperare le ultime notizie, potresti anche voler aggiornare la sezione delle informazioni utente con la raccolta di argomenti preferiti. Nel seguente snippet di codice, un TickHandler espone un SharedFlow in modo che altre classi sappiano quando aggiornare i relativi 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<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() { /* ... */ } // ... }
Puoi personalizzare il comportamento di SharedFlow nei seguenti modi:
replayti consente di inviare nuovamente un numero di valori emessi in precedenza per i nuovi subscriber.onBufferOverflowti consente di specificare un criterio per quando il buffer è pieno di elementi da inviare. Il valore predefinito èBufferOverflow.SUSPEND, che sospende il chiamante. Altre opzioni sonoDROP_LATESToDROP_OLDEST.
MutableSharedFlow ha anche una proprietà subscriptionCount che contiene il numero di collector attivi in modo da poter ottimizzare la logica di business di conseguenza. MutableSharedFlow contiene anche una funzione resetReplayCache se non vuoi riprodurre le ultime informazioni inviate al flusso.
Risorse aggiuntive per i flussi
- Flussi Kotlin su Android
- Testare i flussi Kotlin su Android
- Cosa sapere sugli operatori shareIn e stateIn di Flow
- Eseguire la migrazione da LiveData a Kotlin Flow
- Risorse aggiuntive per le coroutine e i flussi Kotlin