Si te estás metiendo en el mundo de Android, seguro que te has topado con el eterno dilema de cómo manejar los datos en la capa de presentación. Durante mucho tiempo, LiveData fue el rey absoluto, pero con la llegada de las corrutinas de Kotlin, han aparecido herramientas mucho más potentes y flexibles. Hablamos de StateFlow y SharedFlow, que básicamente son la evolución natural para gestionar flujos de datos de manera reactiva y eficiente.
Lo cierto es que, aunque al principio puedan parecer conceptos similares, cada uno tiene su propia razón de ser. No es lo mismo querer que la pantalla recuerde dónde se quedó la lista de noticias que lanzar un aviso rápido de un Toast que solo debe verse una vez. Para no liarnos, vamos a desglosar cómo funcionan estos flujos calientes y por qué deberías considerar dejar atrás LiveData en tus proyectos modernos.
El concepto de Flow: ¿Frío o Caliente?
Para entender StateFlow y SharedFlow, primero hay que pillar la diferencia entre un flujo frío y uno caliente. Un Flow estándar es frío, lo que significa que es como una receta: no ocurre nada hasta que alguien decide ejecutarla (llamar a collect). El problema es que, si tienes tres suscriptores, el código del productor se ejecuta tres veces, lo que puede machacar la batería o hacer peticiones redundantes a la API.
Aquí es donde entran los flujos calientes. Tanto StateFlow como SharedFlow son estratégicamente calientes, lo que implica que el productor permanece activo independientemente de si hay alguien escuchando o no. Esto permite que múltiples colectores compartan la misma fuente de datos, evitando que la app haga el mismo trabajo una y otra vez, algo vital para mantener la fluidez del sistema.
StateFlow: El sustituto ideal para LiveData
StateFlow es, básicamente, un contenedor de estado. Su característica principal es que siempre mantiene un valor actual, el cual puede consultarse de forma síncrona mediante la propiedad .value. Es la herramienta perfecta para gestionar el estado de la interfaz de usuario, como si una pantalla está cargando o si los datos de un perfil se han actualizado correctamente.
A diferencia de LiveData, StateFlow exige un valor inicial en su constructor, lo que elimina la incertidumbre de tener estados nulos al arrancar. Además, implementa un comportamiento de conflación y deduplicación: si emites el mismo valor dos veces seguidas, el flujo simplemente lo ignora. Esto es genial para evitar que la UI se refresque innecesariamente cuando el dato no ha cambiado realmente.
Para implementarlo en un ViewModel, lo más recomendable es usar una propiedad de respaldo MutableStateFlow privada y exponerla como un StateFlow inmutable. Así nos aseguramos de que solo el ViewModel pueda alterar el estado, respetando el flujo de datos unidireccional y evitando que la vista haga travesuras con los datos.
SharedFlow: El bus de eventos para acciones únicas
Si StateFlow es para el «estado», SharedFlow es para los «eventos». Imagina que quieres mostrar un diálogo de error o navegar a otra pantalla. Si usas StateFlow, al rotar el dispositivo, el nuevo colector recibiría el último valor y el evento se dispararía de nuevo, lo cual es un error garrafal. SharedFlow soluciona esto porque puede configurarse con un replay igual a cero.
Este flujo es mucho más flexible y nos permite ajustar parámetros como el buffer de desbordamiento. Si el buffer se llena, podemos elegir que el emisor se suspenda o que se descarte el valor más antiguo o el más reciente. Una ventaja clave es que SharedFlow permite emitir valores repetidos; si necesitas enviar tres veces el mismo aviso de «Error de conexión», SharedFlow lo hará sin pestañear, mientras que StateFlow los filtraría.
Comparativa técnica: StateFlow vs SharedFlow vs LiveData
Para que no haya dudas en una entrevista técnica o al diseñar tu arquitectura, conviene mirar los puntos críticos. LiveData es sencillo y gestiona el ciclo de vida automáticamente, pero está muy ligado a Android. StateFlow y SharedFlow, al ser parte de Kotlin Coroutines, son independientes de la plataforma, lo que facilita enormemente las pruebas unitarias y la creación de capas de dominio limpias.
- StateFlow: Obliga a tener valor inicial, tiene .value, deduplica valores y es ideal para estados de UI.
- SharedFlow: No requiere valor inicial, es configurable en cuanto a replay y es la opción correcta para eventos efímeros.
- LiveData: No requiere valor inicial, es consciente del ciclo de vida pero menos potente en transformaciones asíncronas.
La importancia de la recolección segura
Un error muy común es lanzar un collect dentro de un scope de corrutina simple en la Activity. Esto es peligroso porque el flujo seguirá procesando datos aunque la app esté en segundo plano, lo que puede provocar crashes o un consumo excesivo de CPU. La solución moderna es utilizar la API repeatOnLifecycle(Lifecycle.State.STARTED).
Esta función hace que la recolección se active cuando la vista es visible y se cancele automáticamente al llegar al estado STOPPED. En el caso de Jetpack Compose, la recomendación es usar collectAsStateWithLifecycle(), que integra esta lógica de forma nativa y asegura que la aplicación sea eficiente con los recursos del dispositivo.
Transformando flujos fríos en calientes
A veces tenemos un flujo que viene de una base de datos (como Room) o de un repositorio y es frío. Para convertirlo en uno caliente, usamos los operadores stateIn o shareIn. Estos operadores requieren un CoroutineScope y una estrategia de inicio. La más habitual es SharingStarted.WhileSubscribed(5000).
Esta configuración es brillante porque mantiene el flujo activo mientras haya suscriptores y, si se cierra la pantalla, espera 5 segundos antes de detener el productor. Esto es fundamental para que, durante una rotación de pantalla, el flujo no se destruya y se vuelva a crear, evitando así peticiones innecesarias al servidor o a la base de datos local.
Dominar estas herramientas implica entender que el estado debe ser persistente y deduplicado mediante StateFlow, mientras que las acciones puntuales deben fluir a través de SharedFlow. Al combinar esto con la recolección consciente del ciclo de vida y la transformación de flujos fríos mediante stateIn, conseguimos una arquitectura Android robusta, totalmente reactiva y optimizada para el rendimiento real en dispositivos móviles. Comparte la guía y ayuda a otros a conocer del tema.
Continúar leyendo...