Contenido del curso
Implementación en MVVM
- 4

Navegación y pantallas de onboarding en Compose
26:17 min - 5

Grafo de navegación en Jetpack Compose
12:05 min - 6

Creación de ViewModels para Interacción en Onboarding de Apps
15:56 min - 7

View Models en Android: Gestión de la Lógica de Negocio y UI
17:20 min - 8

Hilt y SharedPreferences en ViewModels Android
29:30 min
Pantallas de Seguimiento
Networking y Datos
Persistencia Local
Funcionalidades Avanzadas
Lanzamiento de la APP
Interacción de Eventos y Estados en UI usando MVVM
Resumen
Aprender a separar eventos y estados en el patrón MVVM te permite construir interfaces reactivas y mantenibles en Jetpack Compose. Si trabajas en una app de tracking de comida con Kotlin, dominar esta separación facilita que tu UI responda con claridad a cada acción del usuario, desde mostrar un loading hasta expandir un ítem al tocarlo.
Por qué separar eventos y estados en el ViewModel
Cuando trabajas con MVVM, el view model gestiona dos cosas distintas: lo que ocurre y lo que se ve. Mezclar ambos vuelve la lógica confusa y difícil de escalar.
¿Cuál es la diferencia entre evento y estado en MVVM? Un evento es algo que sucede y tiene lugar en la UI, como un tap o una búsqueda. Un estado es una condición particular en un momento determinado del componente visual, como
isSearching = true.
Esta diferenciación específica hace que la lógica de interacción sea más concreta y objetiva. Tu pantalla deja de adivinar qué hacer y simplemente reacciona al estado actual [0:35].
Cómo mostrar un loading mientras se busca comida
En el SearchScreen se agrega un Box con modifier = fillMaxSize() y contentAlignment = Alignment.Center. Dentro, una condición evalúa el estado: si isSearching es verdadero, se muestra un CircularProgressIndicator [1:15].
Si la lista trackableFood resulta vacía, aparece un Text con StringResource que dice No results, estilizado con MaterialTheme.typography.bodyMedium y textAlign = Center. Antes, la app buscaba pero no mostraba feedback. Ahora el usuario ve tres estados claros:
- Pantalla inicial con No results.
- Loading mientras se hace la búsqueda.
- Lista de resultados cuando hay coincidencias.
Este cambio convierte una UI muda en una experiencia intuitiva.
Cómo crear un evento OnToggleTrackableFood en Compose
Para que cada ítem de la lista reaccione al tap, necesitas un evento nuevo. En el archivo SearchEvent se agrega una data class OnToggleTrackableFood que recibe una variable de tipo TrackableFood y hereda de SearchEvent [3:05].
Este evento es el puente entre el toque del usuario y el cambio visual del ítem expandido.
Cómo actualizar el estado desde el ViewModel
En el ViewModel, dentro de la función onEvent, se maneja el nuevo caso con is SearchEvent.OnToggleTrackableFood. La actualización se hace con state = state.copy(...), mapeando la lista trackableFood:
- Si el ítem coincide con el
event.food, se cambiaisExpandeda su valor opuesto conit.copy(isExpanded = !it.isExpanded). - Si no coincide, se devuelve
itsin cambios.
¿Qué hace
state.copy()en un ViewModel de Compose? Crea una copia inmutable del estado actual con los valores que cambian. Compose detecta la diferencia y recompone solo lo necesario, evitando renders innecesarios.
Esta validación entre false y true es lo que hace que un mismo ítem se expanda o contraiga sin tocar a los demás.
Cómo construir el componente expandido con BasicTextField
Dentro del TrackableFoodItem, al tocarlo se dispara onEvent(OnToggleTrackableFood(food)) pasando la comida específica para asegurar que se expanda solo esa.
En la Row vacía que estaba reservada, se construye un BasicTextField con estas propiedades:
valueyonValueChangeconectados aamountChanged.keyboardOptionsconimeAction = Donecuando elamountesisNotBlank.keyboardActionsque ejecutadefaultKeyboardAction(ImeAction.Done)al pulsar la tecla principal.singleLine = truepara forzar una sola línea.
Cómo darle estilo y accesibilidad al campo de gramos
El modifier del BasicTextField incluye un border con RoundedCornerShape(5.dp), ancho de 0.5.dp y color MaterialTheme.colorScheme.onSurface. Se alinea con alignBy(LastBaseline) y se le añade padding con spacingMedium.
La propiedad semantics agrega un contentDescription = "Amount", pensado en accesibilidad para personas que usan lectores de pantalla. El sistema describirá que hay un campo de texto que recibe un amount [8:40].
Después del campo, un Spacer con width = spacingExtraSmall separa el texto que muestra gramos con MaterialTheme.typography.bodyLarge y alignBy(LastBaseline).
Cómo agregar el botón de check para trackear
Un IconButton cierra el componente con:
onClick = onTrackpara disparar la acción de guardado.enabled = trackableFoodUiState.amount.isNotBlank()para habilitarlo solo cuando hay valor.IconconImageVector = Icons.Default.CheckycontentDescription = R.string.track.
Usando el preview de Compose con isExpanded = true, ya puedes ver el componente desplegado con caja de texto, gramos y check listo para registrar la comida en la base de datos.
Por qué este patrón mejora tu app de tracking
La separación clara entre eventos y estados te da varias ventajas concretas:
- Cada UI sabe exactamente qué muestra y qué dispara.
- El view model concentra la lógica sin contaminar la vista.
- Probar y depurar se vuelve directo porque cada estado es predecible.
- Agregar nuevas interacciones, como un botón de retroceso, sigue el mismo patrón sin reescribir nada.
¿Te animas a aplicar el mismo principio para agregar la acción de volver desde la pantalla de búsqueda al tracker home screen? Cuéntame en los comentarios cómo lo resolviste.