StateFlow
et SharedFlow
sont des API Flow qui permettent aux flux d'émettre de manière optimale des mises à jour d'état ainsi que des valeurs à plusieurs consommateurs.
StateFlow
StateFlow
est un flux observable d'état qui émet les mises à jour d'état actuelles et nouvelles à ses collecteurs. La valeur d'état actuelle peut également être lue via sa propriété value
. Pour mettre à jour l'état et l'envoyer au flux, attribuez une nouvelle valeur à la propriété value
de la classe MutableStateFlow
.
Dans Android, StateFlow
convient parfaitement aux classes qui doivent conserver un état observable modifiable.
En suivant les exemples des flux Kotlin, un StateFlow
peut être exposé à partir du LatestNewsViewModel
afin que la View
puisse écouter les mises à jour de l'état de l'interface utilisateur et faire en sorte que l'état de l'écran survive aux modifications de configuration.
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 responsable de la mise à jour d'un MutableStateFlow
est le producteur, et toutes les classes effectuant la collecte à partir de StateFlow
sont les consommateurs. Contrairement à un flux froid créé à l'aide du constructeur flow
, un StateFlow
est chaud : la collecte à partir du flux ne déclenche aucun code de producteur. Un StateFlow
est toujours actif et en mémoire, et il ne devient éligible à la récupération de mémoire qu'à partir du moment où aucune autre référence n'y est faite.
Lorsqu'un nouveau consommateur commence la collecte à partir du flux, il reçoit le dernier état du flux et tous les états ultérieurs. Vous pouvez retrouver ce comportement dans d'autres classes observables telles que LiveData
.
La View
écoute StateFlow
comme tout autre flux :
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)
}
}
}
}
}
}
Pour convertir un flux en StateFlow
, utilisez l'opérateur intermédiaire stateIn
.
StateFlow, Flow et LiveData
StateFlow
et LiveData
présentent des similitudes. Ce sont toutes deux des classes de conteneurs de données observables, et elles suivent un modèle similaire lorsqu'elles sont utilisées dans l'architecture de votre application.
Notez toutefois que StateFlow
et LiveData
se comportent différemment :
StateFlow
nécessite qu'un état initial soit transmis au constructeur, ce qui n'est pas le cas deLiveData
.LiveData.observe()
désenregistre automatiquement le consommateur lorsque la vue passe à l'étatSTOPPED
, tandis que la collecte à partir d'unStateFlow
ou de tout autre flux n'arrête pas automatiquement la collecte. Pour obtenir le même comportement, vous devez collecter le flux à partir d'un blocLifecycle.repeatOnLifecycle
.
Transformer les flux froids en flux chauds à l'aide de shareIn
StateFlow
est un flux chaud qui reste en mémoire tant qu'il est collecté ou qu'il existe d'autres références à celui-ci à partir d'une racine de récupération de mémoire. Vous pouvez transformer des flux froids en flux chauds à l'aide de l'opérateur shareIn
.
En prenant comme exemple le callbackFlow
créé dans les flux Kotlin, au lieu de demander à chaque collecteur de créer un nouveau flux, vous pouvez partager les données extraites de Firestore entre les collecteurs en utilisant shareIn
.
Pour ce faire, vous devez transmettre les éléments suivants :
- Un
CoroutineScope
utilisé pour partager le flux. Ce champ d'application doit durer plus longtemps que tout consommateur afin de maintenir le flux partagé en vie aussi longtemps que nécessaire. - Le nombre d'éléments à répéter à chaque nouveau collectionneur.
- La règle du comportement de démarrage.
class NewsRemoteDataSource(...,
private val externalScope: CoroutineScope,
) {
val latestNews: Flow<List<ArticleHeadline>> = flow {
...
}.shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
}
Dans cet exemple, le flux latestNews
répète le dernier élément émis dans un nouveau collecteur et reste actif tant que externalScope
est actif et qu'il existe des collecteurs actifs. La règle de démarrage SharingStarted.WhileSubscribed()
maintient le producteur en amont actif tant que des abonnés sont actifs. D'autres règles de démarrage sont disponibles, telles que SharingStarted.Eagerly
pour démarrer le producteur immédiatement ou SharingStarted.Lazily
pour commencer le partage après l'apparition du premier abonné et garder le flux actif indéfiniment.
SharedFlow
La fonction shareIn
renvoie un SharedFlow
, un flux chaud émettant des valeurs pour tous les consommateurs qui le collectent. Un SharedFlow
est une généralisation hautement configurable de StateFlow
.
Vous pouvez créer un SharedFlow
sans utiliser shareIn
. Par exemple, vous pouvez utiliser un SharedFlow
pour envoyer des ticks au reste de l'application, afin que tout le contenu soit actualisé régulièrement en même temps. En plus de récupérer les dernières nouvelles, vous pouvez actualiser la section des informations sur l'utilisateur avec sa collection de sujets favoris. Dans l'extrait de code suivant, un TickHandler
expose un SharedFlow
afin que les autres classes sachent quand actualiser son contenu. Comme pour StateFlow
, utilisez une propriété de support de type MutableSharedFlow
dans une classe pour envoyer des éléments au flux :
// 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() { ... }
...
}
Vous pouvez personnaliser le comportement de SharedFlow
de différentes façons :
replay
vous permet de renvoyer un certain nombre de valeurs précédemment émises pour les nouveaux abonnés.onBufferOverflow
vous permet de spécifier une règle indiquant quand le tampon contient un grand nombre d'éléments à envoyer. La valeur par défaut estBufferOverflow.SUSPEND
, ce qui entraîne la suspension de l'appelant. Les autres options sontDROP_LATEST
ouDROP_OLDEST
.
MutableSharedFlow
comporte également une propriété subscriptionCount
contenant le nombre de collecteurs actifs afin que vous puissiez optimiser la logique métier en conséquence. MutableSharedFlow
contient également une fonction resetReplayCache
si vous ne souhaitez pas répéter les dernières informations envoyées au flux.
Ressources supplémentaires sur les flux
- Flux Kotlin sur Android
- Tester des flux Kotlin sur Android
- Things to know about Flow's shareIn and stateIn operators (Points importants à retenir concernant les opérateurs shareIn et stateIn de Flow)
- Migrating from LiveData to Kotlin Flow (Migrer de LiveData vers Kotlin Flow)
- Ressources supplémentaires sur les coroutines Kotlin et Flow