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, mentreLiveData
no.LiveData.observe()
annulla automaticamente la registrazione del consumatore quando la visualizzazione passa allo statoSTOPPED
, mentre la raccolta da unStateFlow
o da qualsiasi altro flusso non viene interrotta automaticamente. Per ottenere lo stesso comportamento, devi raccogliere il flusso da un bloccoLifecycle.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 sonoDROP_LATEST
oDROP_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
- Flusso di Kotlin su Android
- Testare i flussi Kotlin su Android
- Informazioni importanti sugli operatori shareIn e stateIn di Flow
- Eseguire la migrazione da LiveData a Kotlin Flow
- Risorse aggiuntive per le coroutine e il flusso di Kotlin