Arquitectura de datos con remember y mutableStateOf

Cuando nos lanzamos de cabeza al mundo de Jetpack Compose, es normal que al principio nos cueste un pelín cambiar el chip. Si vienes del sistema clásico de vistas con XML, acostumbrado a manipular los elementos a mano con un findViewById, te darás cuenta de que aquí la película es totalmente distinta. En Compose, la interfaz es estrictamente declarativa, lo que significa que no andamos retocando la vista, sino que describimos cómo debe verse la pantalla basándonos en la información disponible en cada momento, tal como se explica al crear tu primer proyecto con Jetpack Compose.

Para que esto funcione, necesitamos entender que el estado es cualquier valor que pueda variar con el tiempo, desde una simple variable hasta una base de datos compleja de Room. En esencia, la regla de oro es que si el estado cambia, la interfaz cambia automáticamente. No hace falta sincronizar manualmente la vista con los datos, ya que Compose se encarga de redibujar solo las partes necesarias, un proceso conocido como recomposición, evitando así los típicos errores de estados incoherentes.

El motor del cambio: mutableStateOf y la recomposición

Para que un componente sea capaz de reaccionar a los cambios, no podemos usar variables comunes, ya que Compose no sabría cuándo debe volver a ejecutar la función. Aquí es donde entra en juego mutableStateOf, que crea un contenedor observable. Cuando modificamos el valor interno de este objeto, Compose detecta que algo ha variado e invalida las funciones que estaban leyendo ese dato, disparando la recomposición de forma granular.

Existen diversas formas de declarar este estado para que el código sea más limpio. Podemos usar la sintaxis de delegados con el operador by, que nos permite leer y escribir el valor directamente sin tener que llamar a la propiedad .value constantemente. Para que esto funcione, recuerda que debes importar manualmente getValue y setValue del paquete de runtime de Compose, ya que a veces el IDE se hace el loco y no los añade solo.

La memoria de Compose: el papel de remember

Si declaramos un mutableStateOf directamente dentro de una función composable, nos encontraremos con un problema: cada vez que ocurra una recomposición, el código se ejecutaría desde el principio y el estado se resetearía a su valor inicial. Para evitar que la app pierda la memoria cada dos por tres, utilizamos la API remember, que guarda el valor en la composición durante la primera ejecución y lo recupera en las siguientes.

Es importante mencionar que remember no es omnipotente. Si el usuario rota la pantalla o ocurre un cambio de configuración, la actividad se destruye y el recuerdo se borra, un comportamiento ligado al ciclo de vida de una Activity. Para solucionar esto, tenemos rememberSaveable, que guarda los datos en un Bundle. Si necesitas guardar objetos complejos que no son compatibles con Bundle por defecto, puedes usar la anotación @Parcelize o definir un Saver personalizado mediante mapSaver o listSaver.

Elevación de estado y flujo unidireccional de datos

En el diseño de interfaces, tenemos componentes con estado (Stateful) y componentes sin estado (Stateless). Un componente que gestiona su propio estado internamente suele ser más difícil de testear y menos reutilizable. Para evitar esto, aplicamos el patrón de elevación de estado (State Hoisting), que consiste en mover la variable de estado hacia el componente superior.

Este enfoque implementa el flujo unidireccional de datos (UDF), donde el estado baja hacia los hijos y los eventos suben hacia los padres. Básicamente, sustituimos la variable de estado por dos parámetros: el valor actual y una lambda (como onValueChange) que notifique que se solicita un cambio. Esto garantiza que haya una única fuente de verdad, eliminando la posibilidad de que diferentes partes de la UI muestren datos contradictorios.

Integración con ViewModel y otros observables

El estado bajo control Arquitectura de datos con remember y mutableStateOf

A medida que la app crece, meter toda la lógica de estado en las funciones composables se vuelve un caos. Lo ideal es delegar estas responsabilidades a un contenedor de estado o, más concretamente, a un ViewModel. Compose es muy flexible y permite convertir otros tipos de observables en estados que la UI pueda entender. Por ejemplo, podemos usar collectAsStateWithLifecycle para flujos de Flow o observeAsState para LiveData.

Cuando trabajamos con ViewModels, el flujo es sencillo: la IU genera un evento (como un clic), el ViewModel procesa esa acción y actualiza el estado observable, lo que provoca que la pantalla se actualice automáticamente. Para optimizar el rendimiento, se recomienda usar clases de estado inmutables (data classes) y la función derivedStateOf cuando necesitemos realizar cálculos basados en otros estados, evitando así que la UI se recomponge más veces de las estrictamente necesarias.

La arquitectura moderna de Android se basa en que la interfaz sea un reflejo fiel del estado actual, donde herramientas como remember y mutableStateOf permiten gestionar la memoria y la reactividad, mientras que la elevación de estado y los ViewModels aseguran que la lógica esté separada de la presentación para lograr apps robustas y fáciles de mantener.