Noticia Guía Completa de Gestión de Estado en Jetpack Compose: Dominando remember y mutableStateOf

 Gestión de Estado en Jetpack Compose


Si vienes del mundo de las vistas tradicionales de Android, probablemente estés acostumbrado a pelearte con los ID de los XML y a llamar a métodos como setText para actualizar la pantalla. En el universo de Jetpack Compose, el chip cambia por completo: aquí la interfaz de usuario es una función del estado. Esto significa que no andamos empujando cambios manualmente a la vista, sino que definimos cómo debe lucir la app según los datos actuales y dejamos que el framework se encargue de la magia.

Entrar en este flujo declarativo puede costar un pelín al principio, pero una vez que le pillas el truco, te das cuenta de que es mucho más limpio. La clave de todo reside en entender que el estado es cualquier valor que pueda variar con el tiempo, desde un simple contador de likes hasta una base de datos compleja de Room, y que la UI reacciona a esos cambios de forma orgánica.

El corazón de la reactividad: mutableStateOf​


Para que Compose sepa que tiene que redibujar una parte de la pantalla, no basta con usar una variable común de Kotlin. Necesitamos algo que el sistema pueda «vigilar». Aquí es donde entra mutableStateOf, que crea un contenedor observable. Cuando el valor dentro de este contenedor cambia, Compose marca todas las funciones que leyeron ese valor como «inválidas» y lanza la recomposición.

Hay varias formas de declarar estos estados para que el código quede más limpio. Podemos usar la sintaxis de delegados de propiedad mediante el operador ‘by’, que nos permite tratar el estado como si fuera una variable normal sin tener que escribir .value todo el rato. Para que esto funcione, no olvides importar getValue y setValue del paquete de runtime de Compose, porque a veces el IDE se hace el sueco y no los pone automáticamente.

La memoria de la composición: el papel de remember​


Un error muy típico al empezar es declarar el estado dentro de un composable sin protegerlo. Como la recomposición es básicamente volver a ejecutar la función, las variables normales se reinician en cada ciclo. Si tienes un campo de texto, verás que no escribe nada porque la variable vuelve a quedar vacía milisegundos después de cada tecla pulsada.

Para solucionar esto usamos remember, que almacena el valor en la composición durante la ejecución inicial y lo recupera en las siguientes. Es como decirle a Compose: «Oye, guarda este dato y no lo tires aunque tengas que volver a dibujar el componente». Ahora bien, remember tiene un límite: si rotas el móvil o la actividad se destruye, el dato se pierde. Para esos casos, la solución es rememberSaveable, que guarda la información en un Bundle, permitiendo que el estado sobreviva a los cambios de configuración.

Si necesitas guardar objetos complejos que no entran directamente en un Bundle, tienes varias opciones. Puedes usar la anotación @Parcelize para hacer que tus data classes sean parcelables, o si prefieres más control, implementar un mapSaver o un listSaver para definir exactamente cómo se convierte el objeto en datos guardables y viceversa.

Elevación de Estado (State Hoisting) y Reutilización​


Cuando un componente gestiona su propio estado, decimos que es un componente con estado. Esto está bien para cosas muy simples, pero hace que el componente sea difícil de testear y menos flexible. La solución es el State Hoisting, que consiste en subir el estado a un componente padre. De esta forma, el componente hijo se vuelve «stateless» (sin estado) y solo se encarga de mostrar lo que le piden y notificar los cambios.

El patrón estándar es sustituir la variable de estado por dos parámetros: uno para el valor actual y una función lambda para los eventos (como onValueChange). Esto crea un flujo unidireccional de datos donde el estado baja y los eventos suben, lo que evita que tengamos múltiples fuentes de verdad y reduce drásticamente los errores de sincronización.

Gestión Avanzada: Effects, Flows y ViewModels​


A medida que la app crece, meter toda la lógica en los composables es un suicidio. Lo ideal es delegar esto a contenedores de estado como los ViewModels. Compose se integra genial con LiveData y Flow. Por ejemplo, puedes usar collectAsStateWithLifecycle para recolectar flujos de forma eficiente, asegurando que la app no consuma batería ni recursos cuando la pantalla no es visible.

Para manejar efectos secundarios, Compose ofrece herramientas específicas. LaunchedEffect permite lanzar corrutinas que se cancelan solas cuando el componente sale de la pantalla, mientras que DisposableEffect es perfecto para tareas de limpieza. Si tienes un valor que depende de otros estados, derivedStateOf es tu mejor aliado, ya que evita que se realicen cálculos costosos en cada recomposición si las dependencias no han cambiado realmente.

Optimización y Rendimiento​


No todo es escribir código, también hay que optimizar. La estabilidad es la palabra clave aquí. El uso de tipos inmutables marcados con @Immutable ayuda al compilador de Compose a saber que un objeto no ha cambiado, permitiéndole saltarse la recomposición de ciertos componentes (skipping) y ganar velocidad.

Para listas largas, es imprescindible usar LazyColumn o LazyRow, que solo renderizan lo que es visible en el momento. Además, es recomendable leer el estado lo más abajo posible en el árbol de componentes para que, cuando algo cambie, solo se invalide la pequeña parte de la UI afectada y no toda la pantalla, evitando así tirones en la experiencia del usuario.

Toda esta arquitectura, desde el uso de remember para la memoria local hasta la implementación de MVVM con flujos unidireccionales, permite que el desarrollo de Android sea mucho más predecible. Al separar la lógica de negocio en ViewModels y mantener los componentes de interfaz puros y reactivos, logramos que las aplicaciones sean escalables, fáciles de mantener y, sobre todo, que ofrezcan una navegación fluida y sin errores de estado. Comparte esta información y más usuarios conocerán del tema.

Continúar leyendo...