Gestión de estados de la interfaz de usuario (UIState)

  • Implementación de la Fuente Única de la Verdad y la inmutabilidad para evitar errores de consistencia en la interfaz.
  • Diferenciación entre la lógica de negocio gestionada por ViewModels y la lógica de UI basada en contenedores de estado.
  • Aplicación del flujo unidireccional de datos (UDF) para separar la representación visual de la lógica de estado.

Gestión de estados de la interfaz de usuario (UIState)

Cuando nos metemos en el mundo del desarrollo de aplicaciones modernas, nos damos cuenta de que el estado no es mÔs que cualquier valor capaz de variar con el tiempo. Ya sea una simple variable, una base de datos de Room o una animación que reacciona al tacto, el estado define lo que el usuario ve en cada instante. Dominar cómo se almacena y se actualiza esta información es la diferencia entre una app que va como la seda y una que se llena de errores inexplicables.

En entornos declarativos como Jetpack Compose o React, la mentalidad cambia por completo: ya no modificamos los elementos de la pantalla a mano, sino que describimos la interfaz según el estado actual. Si el estado cambia, la interfaz se redibuja automÔticamente, un proceso conocido como recomposición. Para que esto funcione sin volverse loco, necesitamos patrones claros que eviten que los datos se vuelvan inconsistentes.

Fundamentos del Estado en Jetpack Compose

Para entender Compose, hay que aceptar que la única forma de actualizar la pantalla es volver a llamar a la función con argumentos actualizados. Si intentamos usar un campo de texto sin gestionar su estado, nos daremos cuenta de que no escribe nada; esto sucede porque el componente no se cambia a sí mismo, sino que refleja el valor que recibe.

Para solucionar esto, contamos con la API remember, que permite guardar un objeto en la memoria durante la composición inicial y recuperarlo en las recomposiciones posteriores. Si queremos que ese dato sobreviva a un giro de pantalla o a un cambio de configuración, lo ideal es usar rememberSaveable, que guarda la información en un Bundle del sistema.

Cuando hablamos de variables que deben disparar cambios visuales, recurrimos a mutableStateOf. Este crea un estado observable que, al ser modificado, le dice a Compose que debe ejecutar una recomposición en todas las funciones que estén leyendo ese valor específico.

Patrones Avanzados para un Estado Robusto

No basta con guardar variables; para que una app sea escalable, necesitamos aplicar la Fuente Única de la Verdad (SSOT). Esto implica que todo el estado de la pantalla provenga de un único lugar autoritativo, generalmente mediante clases selladas o interfaces que definan todos los posibles escenarios de la UI, asegurando así que el compilador nos obligue a manejar cada estado.

Otro pilar fundamental es la inmutabilidad del estado. En lugar de modificar un objeto existente, creamos una copia nueva con los cambios deseados (usando el mƩtodo copy de las data classes). Esto evita las temidas condiciones de carrera en entornos concurrentes y hace que las transiciones de estado sean rastreables y fƔciles de depurar.

Para cerrar el círculo, implementamos actualizaciones basadas en eventos. En lugar de que la UI cambie el estado directamente, envía un evento (como un clic o un texto escrito) hacia arriba, y es el dueño del estado quien decide cómo procesarlo. Este modelo es la base del flujo unidireccional de datos (UDF), donde el estado baja hacia los componentes y los eventos suben hacia la lógica.

Contenedores de Estado: ViewModel vs Clases Planas

Dependiendo de la complejidad, podemos elegir dónde alojar la lógica. El ViewModel de Android es la opción estrella para la lógica de negocio. Su gran ventaja es que sobrevive a la recreación de la Activity y se integra perfectamente con Navigation y Hilt, permitiendo que los datos persistan mientras el usuario navega por la app.

Por otro lado, existen los contenedores de estado de lógica de UI, que suelen ser clases sencillas sin formato. Estos se encargan de cosas mÔs efímeras, como si un botón debe mostrarse según la posición del scroll o la gestión de permisos. A diferencia del ViewModel, estos tienen el mismo ciclo de vida que la interfaz y se suelen crear mediante funciones remember personalizadas.

Es vital no pasar el ViewModel directamente a los componentes hijos, ya que esto acoplaría la vista a la lógica y dificultaría las pruebas. Lo correcto es pasar solo el estado necesario y delegar los eventos mediante lambdas, manteniendo así la modularidad y la facilidad de crear vistas previas.

Estrategias de Pruebas y Calidad

Gestión de estados de la interfaz de usuario (UIState)

Cuando el estado es inmutable y se basa en eventos, testear la aplicación se vuelve una tarea sencillamente placentera. Podemos verificar que el ViewModel produzca la actualización esperada ante un evento concreto sin necesidad de lanzar la interfaz grÔfica, haciendo aserciones directas sobre el estado resultante.

En cuanto a las pruebas de UI, el foco debe estar en comprobar que los componentes reflejan fielmente el estado y que las interacciones del usuario disparan los eventos correctos. Al separar la lógica de la presentación, podemos probar cada pieza por separado, asegurando que los estados de error o carga se muestren adecuadamente.

Gestión de Estados en el Ecosistema React

Aunque hablemos de Android, los conceptos son primos hermanos de lo que ocurre en React. Aquí también existe el estado local mediante el hook useState, que permite a cada componente manejar sus propios datos internos. Cuando necesitamos que varios componentes compartan información, utilizamos las props para pasar datos del padre al hijo.

Para evitar el problema de pasar props a travƩs de demasiados niveles (prop drilling), React ofrece el contexto mediante useContext, creando un almacƩn centralizado de datos. En aplicaciones de gran escala, se suele recurrir a Redux, que implementa un Store, Acciones y Reducers para gestionar el estado de forma predecible y global.

Al igual que en Compose, en React no se manipula el DOM manualmente, sino que se describe la interfaz para diferentes estados visuales. El flujo de datos sigue siendo la clave: el estado se actualiza mediante funciones específicas y esto provoca que los componentes se vuelvan a renderizar con la nueva información.

Tener una arquitectura donde el estado sea predecible y la lógica esté separada de la vista permite que el desarrollo sea mucho mÔs fluido. Ya sea utilizando ViewModels en Android, Redux en la web o simplemente elevando el estado a un componente padre, la clave reside en mantener la coherencia de los datos y respetar el flujo unidireccional para que la aplicación sea mantenible a largo plazo.


Add as preferred source