Creación de un ViewModel para Gestión de Tareas en Pantalla

Clase 13 de 19Curso de Jetpack Compose en Android

Contenido del curso

Resumen

Conectar una interfaz de usuario con la lógica de negocio es uno de los pasos más importantes al construir aplicaciones Android modernas. Aquí se aborda cómo construir un ViewModel que gestione estados, acciones y eventos para una pantalla de creación de tareas, utilizando patrones como sealed interfaces, channels y TextFieldState.

¿Cómo separar el estado de la pantalla en una clase independiente?

El primer paso consiste en extraer el TaskScreenState del archivo de la vista y llevarlo a su propio archivo dentro del paquete details [01:05]. Esto mejora la organización del código y facilita el mantenimiento.

Un cambio relevante es reemplazar los String que rastrean el contenido de los campos de texto por TextFieldState, una clase especializada que gestiona el estado del texto sin necesidad de crear variables independientes para cada campo [01:26]. Con esto se evita la duplicación de lógica y se simplifica el seguimiento de cambios en los text fields.

¿Qué son las acciones y eventos en el patrón ViewModel?

Para describir las interacciones posibles en la pantalla de detalle, se crea una sealed interface llamada ActionTask [02:01]. Las acciones definidas son:

  • SaveTask: guarda la tarea creada.
  • Back: regresa a la lista de tareas.
  • ChangeTaskCategory: recibe una categoría (puede ser nula) y actualiza el estado.
  • ChangeTaskDone: marca la tarea como completada desde su creación o edición.

Los eventos, por su parte, se modelan en otra sealed interface llamada TaskEvents con un único evento: TaskSaved [03:30]. Este evento dispara una notificación visual (un toast) cuando la tarea se guarda correctamente.

¿Cómo se estructura el ViewModel?

El TaskViewModel hereda de la clase ViewModel y contiene tres elementos clave [03:52]:

  • Estado mutable: una variable state inicializada con mutableStateOf(TaskScreenState()), con private set para que la vista no pueda modificarlo directamente.
  • Canal de eventos: un Channel<TaskEvents> marcado como privado, cuyo flujo se expone como Flow mediante receiveAsFlow().
  • FakeTaskLocalDataSource: la fuente de datos local simulada que almacena las tareas.

La función onAction utiliza una sentencia when para discriminar cada acción [04:55]:

  • En ChangeTaskCategory, se actualiza el estado con state.copy(category = action.category).
  • En ChangeTaskDone, se copia el estado con el nuevo valor de done.
  • En SaveTask, se construye un objeto Task con un ID generado mediante UUID aleatorio convertido a string, el título y descripción extraídos del TextFieldState mediante text.toString(), el estado de completado y la categoría [05:45].

La operación de guardar se ejecuta dentro de viewModelScope.launch porque addTask es una suspend function y requiere una corrutina [06:30]. Tras guardar, se envía el evento TaskCreated a través del canal.

¿Cómo conectar el ViewModel con la pantalla composable?

Se crea una función composable TaskScreenRoot que instancia el ViewModel y extrae el estado y los eventos [07:05]. Para escuchar los eventos se utiliza events.collect dentro de un LaunchedEffect, discriminando con when y mostrando un Toast con el mensaje "Task saved" cuando llega TaskCreated [07:35].

Dentro de TaskScreenRoot se invoca TaskScreen pasando el estado y una lambda onAction que delega al ViewModel.

¿Qué ajustes requiere la UI al usar TextFieldState?

Al cambiar de String a TextFieldState, varios componentes necesitan actualizarse [08:20]:

  • Se reemplaza decorationBox por decorator.
  • Se elimina onValueChanged porque TextFieldState gestiona los cambios internamente.
  • Se sustituye maxLines por lineLimits usando TextFieldLineLimits.singleLine para el nombre de la tarea.
  • Las validaciones de campo vacío cambian a state.text.toString().isEmpty().

En los previews, las variables de tipo String se reemplazan por instancias de TextFieldState inicializadas con valores de prueba [09:45].

Para la navegación, se agrega un ícono de retroceso en el navigationIcon del Scaffold usando Icons.AutoMirrored.Filled.ArrowBack, con un Modifier.clickable que ejecuta onAction(ActionTask.Back) [10:25].

Finalmente, el Checkbox dispara ChangeTaskDone, la selección de categoría en el menú desplegable ejecuta ChangeTaskCategory, y el botón de guardar llama a onAction(ActionTask.SaveTask) [11:30]. Al probar la aplicación, se confirma que el formulario funciona correctamente: los campos capturan texto, el checkbox se activa, las categorías se despliegan y la tarea se guarda mostrando el toast de confirmación [12:10].

¿Ya lograste conectar tu ViewModel con la pantalla de creación? Comparte un screenshot del estado actual de tu proyecto y prepárate para integrar la navegación entre la lista y la creación de tareas.