Contenido del curso
Personalización de la UI con Material Design
Composición de layouts en Jetpack Compose
- 6

Modelo de datos para una to do app
18:48 min - 7

Creación de Componentes UI en Jetpack Compose: Summary y Task Item
17:36 min - 8

HomeScreen con Scaffold en Jetpack Compose
23:16 min - 9

Creación de un Arco de Progreso Animado en Compose
14:39 min - 10

Gestión de Acciones y Estados en ViewModel de Android
23:14 min - 11

Formateo y Ajustes Finales en Pantalla Home de To Do App
04:17 min
Construcción de funcionalidades
Navegación en Jetpack Compose
Creación de Bases de datos y dependencias
Finalización de la app
Creación de un ViewModel para Gestión de Tareas en Pantalla
Resumen
Conectar la interfaz de creación de tareas con su lógica requiere un ViewModel que gestione estados, acciones y eventos en Jetpack Compose. Aquí aprendes a estructurarlo usando TextFieldState, sealed interfaces y un canal de eventos para feedback al usuario.
Cómo separar el estado de la pantalla en una clase independiente
El primer paso es mover TaskScreenState a su propio archivo dentro del paquete details. Esto mantiene el código modular y facilita el mantenimiento cuando la pantalla crece.
Dentro de ese estado, en lugar de rastrear los campos de texto con un String, conviene usar TextFieldState, una clase especializada de Compose que se encarga del seguimiento por ti. Así evitas declarar variables de estado independientes para cada TextField y reduces el código repetitivo.
¿Qué es TextFieldState en Jetpack Compose? Es un state holder especializado que gestiona el contenido de un campo de texto sin que tengas que mantener un String mutable por separado. Simplifica el manejo de formularios.
Cómo definir acciones y eventos con sealed interfaces
Siguiendo el mismo patrón de la lista de tareas, modelas las interacciones del usuario como una sealed interface llamada ActionTask. Cada acción representa algo que puede ocurrir en la UI [02:10].
Las acciones necesarias son:
- SaveTask: como data object, dispara el guardado de la tarea.
- Back: como data object, indica retorno a la lista.
- ChangeTaskCategory: como data class que recibe una
Categorynullable. - ChangeTaskDone: como data class para alternar el estado de completado.
Los eventos viven en otra sealed interface y, en este caso, basta con uno: TaskSaved. Este evento notifica a la UI que la tarea se persistió correctamente para mostrar un toast.
Cómo construir el TaskViewModel paso a paso
La clase TaskViewModel hereda de ViewModel e instancia el FakeTaskLocalDataSource para persistir datos en memoria [04:30]. Dentro declaras tres piezas clave.
Cómo exponer el estado con mutableStateOf
El estado se expone como una variable mutableStateOf<TaskScreenState> con inicialización by lazy y private set. El private set impide que la vista modifique el estado directamente, manteniendo la unidirección del flujo de datos.
Cómo crear un canal de eventos en Kotlin
Los eventos se emiten a través de un Channel<TaskEvent> privado, expuesto como Flow mediante receiveAsFlow(). Así la UI los consume una sola vez sin reproducirlos en recomposiciones.
¿Para qué sirve un Channel en un ViewModel? Permite enviar eventos puntuales (como mostrar un toast o navegar) que no deben quedar guardados en el estado ni dispararse dos veces.
Cómo manejar acciones con when y viewModelScope
La función onAction(action: ActionTask) usa una sentencia when para discriminar cada acción:
ChangeTaskCategoryactualiza el estado constate.copy(category = ...).ChangeTaskDonealterna el flag de completado.SaveTaskconstruye un objetoTasky lo persiste.Backse delega a la navegación, igual que en la lista.
Para guardar, generas un ID único con UUID.randomUUID().toString(), tomas el título y descripción desde los TextFieldState accediendo a su charSequence y convirtiendo a String, y llamas a fakeTaskLocalDataSource.addTask(task). Como addTask es una suspend function, debes envolverla en viewModelScope.launch [07:45]. Tras guardar, emites eventChannel.send(TaskEvent.TaskSaved).
Cómo conectar el ViewModel con la pantalla composable
La pantalla se divide en dos: TaskScreenRoot (con acceso al ViewModel) y TaskScreen (puramente presentacional). Esta separación facilita los previews y las pruebas.
En TaskScreenRoot instancias el ViewModel con viewModel(), extraes el estado y observas los eventos con events.collect. Cuando llega TaskSaved, obtienes el contexto con LocalContext.current y muestras un Toast.makeText con un string resource llamado task_saved.
La lambda onAction: (ActionTask) -> Unit se conecta directamente con viewModel::onAction, manteniendo a TaskScreen desacoplada del ViewModel.
Cómo ajustar los TextFields al nuevo estado
Al cambiar de String a TextFieldState, los campos requieren ajustes:
- Para validar vacío usa
state.taskName.text.toString().isEmpty(). - Reemplaza
valueporstatedirectamente. - Cambia
decorationBoxpordecorator. - Elimina
onValueChangeporque el estado se actualiza solo. - Sustituye
maxLinesporlineLimits = TextFieldLineLimits.SingleLineen el campo de nombre.
Los previews también se actualizan: en lugar de pasar Strings, inicializas TextFieldState("task 1") para cada caso.
Cómo cablear las acciones a los componentes de la UI
En el Scaffold, el navigationIcon de la top bar usa Icons.AutoMirrored.Filled.ArrowBack con un tint basado en colorScheme.onSurface y un Modifier.clickable { onAction(ActionTask.Back) }.
El Checkbox de completado dispara onAction(ActionTask.ChangeTaskDone). El menú desplegable de categorías cierra con isExpanded = false y emite onAction(ActionTask.ChangeTaskCategory(category)) al seleccionar. El botón de guardar dispara ActionTask.SaveTask.
Al probar desde MainActivity invocando TaskScreenRoot, ya puedes escribir el nombre, ver cómo crece la descripción, marcar como completada, elegir entre work, personal, shopping u other, y recibir el toast de confirmación al guardar.
Con esto tienes dos pantallas funcionales: la lista y la creación. ¿Cómo conectarías ahora la navegación entre ambas? Comparte un screenshot de tu proyecto y nos vemos en la siguiente clase para enlazarlas.