StateFlow
e SharedFlow
sono API di flusso
che consentono ai flussi di inviare aggiornamenti di stato in modo ottimale ed emettere valori a più
consumer.
StateFlow
StateFlow
è un flusso osservabile degli stati che emette gli aggiornamenti attuali e nuovi dello stato
ai propri raccoglitori. Il valore dello stato attuale 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 modificabile osservabile.
Seguendo gli esempi dei flussi Kotlin, un StateFlow
può essere esposto da LatestNewsViewModel
in modo che View
possa ascoltare gli aggiornamenti dello stato dell'interfaccia utente e far sì che lo stato della schermata 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 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 producer, mentre tutte le classi che vengono raccolte da StateFlow
sono i consumatori. A differenza di un flusso a freddo creato con il builder di flow
, un StateFlow
è hot: la raccolta dal flusso non attiva alcun codice producer. Un elemento StateFlow
è sempre attivo e in memoria e diventa idoneo per la garbage collection solo quando non esistono altri riferimenti da una radice di garbage collection.
Quando un nuovo consumatore inizia a raccogliere dati dal flusso, riceve l'ultimo stato del flusso e tutti gli 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 StateFlow
, utilizza l'operatore intermedio
stateIn
.
StateFlow, Flow e LiveData
StateFlow
e LiveData
hanno
somiglianze. Entrambe sono classi di titolari di dati osservabili ed entrambe seguono uno schema simile se utilizzate nell'architettura della tua app.
Tuttavia, tieni presente 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 consumer quando la visualizzazione passa allo statoSTOPPED
, mentre la raccolta da unStateFlow
o da qualsiasi altro flusso non interrompe la raccolta automatica. Per ottenere lo stesso comportamento, devi raccogliere il flusso da un bloccoLifecycle.repeatOnLifecycle
.
Riscaldamento del flusso freddo con shareIn
StateFlow
è un flusso ad accesso frequente: rimane in memoria finché il flusso viene raccolto o fintanto che esistono altri riferimenti al flusso da una radice di garbage collection. Puoi scaldare i flussi freddi utilizzando l'operatore shareIn
.
Utilizzando come esempio il callbackFlow
creato nei flussi Kotlin, anziché lasciare che ogni raccoglitore crei un nuovo flusso, puoi condividere i dati recuperati da Firestore tra i raccoglitori utilizzando shareIn
.
Devi superare quanto segue:
- Un elemento
CoroutineScope
utilizzato per condividere il flusso. Questo ambito deve durare più a lungo di qualsiasi consumatore per mantenere attivo il flusso condiviso il tempo necessario. - Il numero di elementi da riprodurre per ogni nuovo raccoglitore.
- Il criterio relativo al 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
ripete l'ultimo elemento emesso in un nuovo raccoglitore e rimane attivo finché externalScope
è attivo e sono presenti raccoglitori attivi. Il criterio di avvio SharingStarted.WhileSubscribed()
mantiene attivo il producer upstream mentre sono presenti abbonati attivi. Sono disponibili altri criteri di avvio, ad esempio SharingStarted.Eagerly
per avviare il producer immediatamente o SharingStarted.Lazily
per avviare la condivisione dopo la visualizzazione del primo abbonato e mantenere attivo il flusso per sempre.
Flusso condiviso
La funzione shareIn
restituisce SharedFlow
, un flusso ad accesso frequente che emette valori
a tutti i consumer che li raccolgono. Un SharedFlow
è una generalizzazione di StateFlow
altamente configurabile.
Puoi creare un SharedFlow
senza utilizzare shareIn
. Ad esempio, potresti
utilizzare un valore 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, ti consigliamo di aggiornare la sezione delle informazioni utente con la sua raccolta di argomenti preferiti. Nel seguente snippet di codice, un oggetto TickHandler
espone un SharedFlow
in modo che le altre classi sappiano quando aggiornare i propri contenuti. Come con 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
consente di specificare un criterio per stabilire quando il buffer è pieno di elementi da inviare. Il valore predefinito èBufferOverflow.SUSPEND
, che fa sospendere il chiamante. Altre opzioni sonoDROP_LATEST
oDROP_OLDEST
.
MutableSharedFlow
ha anche una proprietà subscriptionCount
contenente il numero di raccoglitori attivi in modo che tu possa ottimizzare di conseguenza la logica di business. MutableSharedFlow
contiene anche una funzione resetReplayCache
se non vuoi riprodurre le informazioni più recenti inviate al flusso.
Risorse di flusso aggiuntive
- Flussi di Kotlin su Android
- Test dei flussi Kotlin su Android
- Informazioni sugli operatori shareIn e stateIn di Flow
- Migrazione da LiveData a Kotlin Flow
- Risorse aggiuntive per coroutine e flow Kotlin