StateFlow
y SharedFlow
son API de Flow que permiten que los flujos emitan actualizaciones de estado y valores a varios consumidores de manera óptima.
StateFlow
StateFlow
es un flujo observable contenedor de estados que emite actualizaciones de estado actuales y nuevas a sus recopiladores. El valor de estado actual también se puede leer a través de su propiedad value
. Para actualizar el estado y enviarlo al flujo, asigna un nuevo valor a la propiedad value
de la clase MutableStateFlow
.
En Android, StateFlow
es una excelente opción para clases que necesitan mantener un estado observable que muta.
De acuerdo con los ejemplos de los flujos de Kotlin, se puede exponer un StateFlow
del LatestNewsViewModel
para que View
pueda detectar actualizaciones de estado de la IU y, de manera inherente, permitir que el estado de la pantalla se conserve después de hacer cambios en la configuración.
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 clase responsable de actualizar un MutableStateFlow
es el productor, mientras que todas las clases que se recopilan de StateFlow
son consumidores. A diferencia de un flujo frío compilado con el compilador de flow
, un StateFlow
es caliente; recopilar datos del flujo no activa ningún código de productor. Un objeto StateFlow
siempre se encuentra activo y en la memoria, y se vuelve apto para la recolección de elementos no utilizados solo cuando no hay otras referencias a él en la raíz de otra recolección.
Cuando un consumidor nuevo comienza a recopilarse desde el flujo, recibe el último estado del flujo y todos los estados posteriores. Puedes encontrar este comportamiento en otras clases observables, como LiveData
.
El View
detecta un elemento StateFlow
de la misma manera que cualquier otro flujo:
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)
}
}
}
}
}
}
Para convertir cualquier flujo en un StateFlow
, usa el operador intermedio stateIn
.
StateFlow, Flow y LiveData
StateFlow
y LiveData
tienen similitudes. Ambas son clases contenedoras de datos observables y siguen un patrón similar cuando se usan en la arquitectura de tu app.
Sin embargo, ten en cuenta que StateFlow
y LiveData
se comportan de manera diferente:
StateFlow
requiere que se pase un estado inicial al constructor, mientras queLiveData
, no.LiveData.observe()
cancela automáticamente el registro del consumidor cuando la vista pasa al estadoSTOPPED
, mientras que la recopilación deStateFlow
o cualquier otro flujo, no deja de recopilar automáticamente. Para obtener el mismo comportamiento, debes recopilar el flujo desde un bloqueLifecycle.repeatOnLifecycle
.
Cómo convertir flujos fríos en calientes con shareIn
StateFlow
es un flujo caliente, es decir, permanece en la memoria siempre que se recopile o mientras que una raíz de recolección de elementos no utilizados origine alguna referencia a él. Puedes usar el operador shareIn
para cambiar los flujos fríos a calientes.
Si usas el callbackFlow
creado en flujos de Kotlin como ejemplo, en lugar de hacer que cada recopilador cree un flujo nuevo, puedes compartir los datos recuperados de Firestore entre recopiladores con shareIn
.
Debes pasar lo siguiente:
- Un
CoroutineScope
que se use para compartir el flujo (este alcance debe durar más que cualquier consumidor para mantener el flujo compartido activo el tiempo que sea necesario) - La cantidad de elementos que se volverán a reproducir en cada recopilador nuevo
- La política de comportamiento de inicio
class NewsRemoteDataSource(...,
private val externalScope: CoroutineScope,
) {
val latestNews: Flow<List<ArticleHeadline>> = flow {
...
}.shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
}
En este ejemplo, el flujo latestNews
vuelve a reproducir el último elemento emitido en un recopilador nuevo y permanecerá activo mientras externalScope
esté activo y haya recopiladores activos. La política de inicio SharingStarted.WhileSubscribed()
mantendrá activo el productor ascendente mientras haya suscriptores activos. También hay otras políticas de inicio disponibles, como SharingStarted.Eagerly
para iniciar inmediatamente el productor o SharingStarted.Lazily
para empezar a compartir después de que aparece el primer suscriptor y mantener el flujo activo de forma permanente.
SharedFlow
La función shareIn
muestra un SharedFlow
, un flujo caliente que emite valores para todos los consumidores que recopilan datos de él. Un SharedFlow
es una generalización que admite una amplia configuración de StateFlow
.
Puedes crear un SharedFlow
sin usar shareIn
. Como ejemplo, puedes usar un SharedFlow
para enviar marcas al resto de la app, de modo que todo el contenido se actualice simultáneamente y de manera periódica. Además de recuperar las noticias más recientes, puedes actualizar la sección de información del usuario con su colección de temas favoritos. En el siguiente fragmento de código, un TickHandler
expone un elemento SharedFlow
para que otras clases sepan cuándo actualizar su contenido. Al igual que con StateFlow
, usa una propiedad de copia de seguridad de tipo MutableSharedFlow
en una clase para enviar elementos al flujo:
// 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() { ... }
...
}
Puedes personalizar el comportamiento de SharedFlow
de las siguientes maneras:
replay
te permite volver a enviar una serie de valores previamente emitidos para suscriptores nuevos.onBufferOverflow
te permite especificar una política para las ocasiones en que el búfer está lleno de elementos que se enviarán. El valor predeterminado esBufferOverflow.SUSPEND
, lo que provoca la suspensión del emisor. Otras opciones sonDROP_LATEST
oDROP_OLDEST
.
MutableSharedFlow
también tiene una propiedad subscriptionCount
, que contiene la cantidad de recopiladores activos para que puedas optimizar tu lógica empresarial según corresponda. MutableSharedFlow
también contiene una función resetReplayCache
en caso de que no desees volver a reproducir la información más reciente enviada al flujo.
Recursos adicionales de flujo
- Flujos de Kotlin en Android
- Cómo probar los flujos de Kotlin en Android
- Información sobre los operadores ShareIn y stateIn de Flow
- Migración de LiveData a Kotlin Flow
- Recursos adicionales para las corrutinas y el flujo de Kotlin