Gestión de estados con remember y mutableStateOf

  • El estado en Compose es el motor que dispara la recomposición de la interfaz de usuario.
  • La combinación de remember y mutableStateOf permite persistir datos durante el ciclo de vida de un componente.
  • El patrón de elevación de estado (State Hoisting) separa la lógica de negocio de la representación visual.
  • Herramientas como rememberSaveable y collectAsStateWithLifecycle aseguran la persistencia y eficiencia de los recursos.

Gestión de estados con remember y mutableStateOf

Si vienes del mundo de las vistas clásicas de Android, probablemente estés acostumbrado a pelearte con el <code]findViewById y a modificar los elementos de la pantalla a mano. Sin embargo, Jetpack Compose nos propone un <strong]cambio de chip radical, moviéndonos hacia un modelo declarativo donde la interfaz no se manipula, sino que se describe en función de un estado actual.

En esencia, cualquier dato que pueda variar con el tiempo, desde una simple cadena de texto en un formulario hasta una base de datos compleja en Room, se considera estado. Para que nuestra app no se quede «congelada» y reaccione a lo que el usuario hace, necesitamos que Compose sepa exactamente <strong]cuándo debe redibujar la pantalla, un proceso que conocemos técnicamente como recomposición.

El corazón del estado: mutableStateOf y la Recomposición

Para que un elemento de la interfaz se entere de que algo ha cambiado, no basta con usar una variable común de Kotlin. Necesitamos un <strong]contenedor observable que avise al sistema de Compose que el valor ha variado. Aquí es donde entra en juego <code]mutableStateOf, que crea un objeto <code]MutableState capaz de disparar la actualización de todas las funciones que lean dicho valor.

Cuando el valor de este objeto cambia, Compose marca la función como «inválida» y la ejecuta de nuevo. Esto es lo que llamamos <strong]recomposición. Si intentas usar un <code]TextField sin un estado vinculado, verás que por más que escribas, el texto no aparece; esto ocurre porque el componente no se actualiza solo, sino que necesita que el parámetro <code]value reciba un dato nuevo explícitamente.

Existen diversas formas de escribir estas declaraciones para que el código quede más limpio. Podemos usar la sintaxis de delegados con la palabra clave <code]by, lo que nos evita tener que escribir <code].value cada vez que queremos acceder al dato, aunque esto requiere importar <code]getValue y <code]setValue del paquete de runtime de Compose utilizando Kotlin para programar en Android.

novedades de Google Android Studio 3.2
Artículo relacionado:
Guía definitiva para descargar Android Studio y crear aplicaciones Android paso a paso

Haciendo que la memoria funcione con remember

Un problema recurrente al empezar es que la recomposición es, básicamente, volver a ejecutar la función. Si declaramos el estado directamente dentro del Composable, <strong]se reiniciará en cada ciclo, volviendo al valor inicial y perdiendo cualquier cambio realizado por el usuario. Para evitar este comportamiento, utilizamos la API <code]remember.

La función <code]remember actúa como un almacén que guarda el valor durante la primera composición y lo recupera en las siguientes. Es ideal para almacenar objetos costosos de calcular o simples estados locales. No obstante, debemos tener cuidado: <code]remember olvida todo si el componente sale del árbol de composición o si ocurre un <strong]cambio de configuración, como rotar la pantalla.

Para solucionar el problema de las rotaciones, disponemos de <code]rememberSaveable. Esta herramienta guarda el estado en un <code]Bundle, permitiendo que la información sobreviva incluso si la actividad se destruye y recrea, gestionando correctamente el ciclo de vida de una activity en Android. Si queremos guardar objetos complejos que no son compatibles con el Bundle por defecto, podemos recurrir a la anotación <code]@Parcelize o implementar un <code]Saver personalizado mediante <code]mapSaver o <code]listSaver.

mvvm
Artículo relacionado:
MVVM: El patrón de arquitectura de software definitivo para apps modernas

Elevación de Estado o State Hoisting

Tener el estado dentro de un componente lo hace más fácil de usar al principio, pero lo convierte en un elemento rígido, difícil de testear y menos reutilizable. La técnica de <strong]elevación de estado consiste en mover el estado hacia arriba, hacia el componente padre, transformando al componente hijo en uno «sin estado» (stateless).

En la práctica, esto significa sustituir la variable de estado por dos parámetros: uno que represente el <strong]valor actual a mostrar y otro que sea una función lambda para notificar que el valor debe cambiar. Este patrón crea un <strong]flujo unidireccional de datos: el estado baja hacia los hijos y los eventos suben hacia los padres.

Seguir este esquema tiene ventajas brutales. Por un lado, tenemos una <strong]fuente única de verdad, lo que evita que los datos se desincronicen. Por otro, podemos mover fácilmente ese estado a un <code]ViewModel, permitiendo que la lógica de negocio esté totalmente separada de la parte visual.

Optimización Avanzada y Efectos Secundarios

Gestión de estados con remember y mutableStateOf

A medida que las apps crecen, el rendimiento se vuelve crítico. No queremos que toda la pantalla se recomponga por un cambio insignificante. Para ello, Compose ofrece <code]derivedStateOf, que crea un estado calculado que solo se actualiza cuando sus dependencias cambian, evitando <strong]recomposiciones innecesarias en listas largas o cálculos complejos.

Cuando necesitamos interactuar con código que no es de Compose (como lanzar una petición de red o configurar un listener), utilizamos los Effects. <code]LaunchedEffect es la joya de la corona para manejar <strong]corrutinas que deben cancelarse cuando el componente desaparece, mientras que <code]DisposableEffect es fundamental para realizar tareas de limpieza y evitar fugas de memoria.

Para integrar flujos de datos modernos, como Flow o LiveData, contamos con funciones de extensión como <code]observeAsState o <code]collectAsStateWithLifecycle. Esta última es la opción recomendada en Android, ya que es <strong]consciente del ciclo de vida y detiene la recolección de datos cuando la app no es visible, ahorrando batería y recursos del dispositivo.

Control de Claves y Estabilidad

A veces queremos que un valor recordado se actualice no solo por recomposición, sino porque una variable externa ha cambiado. <code]remember y <code]rememberSaveable permiten pasar <strong]claves de entrada. Si la clave cambia, la caché se invalida y el bloque de código se vuelve a ejecutar, asegurando que la UI siempre esté sincronizada con los datos de entrada.

Para mejorar la eficiencia, es vital usar clases marcadas como <code]@Immutable o <code]@Stable. Esto le dice al compilador de Compose que el objeto no cambiará de forma inesperada, permitiendo que el sistema <strong]se salte la recomposición de ciertos componentes si sus parámetros no han variado, optimizando así la fluidez de la aplicación.

Dominar la interacción entre <code]mutableStateOf para la observabilidad, <code]remember para la persistencia local y el <strong]state hoisting para la arquitectura, es lo que diferencia a un desarrollador principiante de uno experto en Compose. Al combinar estas herramientas con una gestión inteligente de los efectos y la integración de flujos de datos, logramos aplicaciones robustas donde la interfaz es un reflejo fiel y eficiente del estado interno.


Add as preferred source