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
Viendo ahora - 8

Hilt y SharedPreferences en ViewModels Android
29:30 min
Pantallas de Seguimiento
Networking y Datos
Persistencia Local
Funcionalidades Avanzadas
Lanzamiento de la APP
View Models en Android: Gestión de la Lógica de Negocio y UI
Resumen
Cuando trabajas con MVVM en Android, cada pantalla necesita su propio ViewModel para mantener la lógica de negocio separada de la UI. Aquí aprenderás a construir un ViewModel para una caja de texto de edad en Jetpack Compose, con validaciones, manejo de eventos y notificaciones tipo snack bar, siguiendo principios SOLID que harán tu código más testeable y mantenible.
Por qué cada pantalla debe tener su propio ViewModel
El ViewModel gestiona toda la lógica de negocio y el estado de la UI, manteniéndolos desacoplados de la vista. Esto te asegura que cuando el usuario rote el dispositivo o cambie el idioma, valores como el contenido de una caja de texto no se pierdan ni se reinicien.
Asignar un ViewModel por pantalla aporta tres beneficios concretos:
- Cumple el principio de responsabilidad única de SOLID.
- Facilita las pruebas unitarias al aislar la lógica.
- Mejora el mantenimiento porque cada pantalla evoluciona de forma independiente.
¿Qué hace un ViewModel en MVVM? Gestiona la lógica de negocio y el estado de la UI de una pantalla. Sobrevive a cambios de configuración como rotación o cambio de idioma, manteniendo los datos intactos.
Cómo configurar el Scaffold y el SnackbarHost en MainActivity
El scaffold es un composable de Jetpack Compose que da congruencia visual al proyecto y permite integrar componentes globales como el SnackbarHost [01:30]. Este componente muestra mensajes flotantes, equivalentes a los antiguos snack bars de Android, ideales para reportar errores de validación al usuario.
Dentro del MainActivity se declara un estado recordable:
kotlin val snackBarHostState = remember { SnackbarHostState() }
Ese snackBarHostState se asigna al scaffold y se pasa por referencia al NavigationRoot, para que cualquier pantalla hija pueda mostrar mensajes desde el mismo host. Si aparece un warning por el parámetro de padding, basta con suprimirlo con @Suppress("UnusedMaterial3ScaffoldPaddingParameter").
Cómo construir el AgeViewModel paso a paso
El AgeViewModel extiende de ViewModel de Android y por ahora se inyecta de forma manual; más adelante se puede migrar a Hilt con inyección de dependencias [03:20].
Cómo definir el estado mutable de la edad
La edad necesita ser mutable porque el usuario la cambiará constantemente. Para eso se usa mutableStateOf, con un valor por defecto de 20:
kotlin var age by mutableStateOf("20") private set
También se incorpora una clase helper llamada filterOutDigits, creada en clases anteriores, que filtra cualquier carácter que no sea un dígito. Así te aseguras de que la edad nunca contenga letras ni símbolos extraños.
Cómo emitir eventos de UI con Channel y Flow
Para comunicar acciones desde el ViewModel hacia la pantalla se usa un Channel expuesto como Flow [04:50]. Este canal transporta los estados definidos en UiEvent, como Success (navegar a la siguiente pantalla) o ShowSnackBar (mostrar un mensaje de error).
kotlin private val _uiEvent = Channel<UiEvent>() val uiEvent = _uiEvent.receiveAsFlow()
Cómo validar la entrada con onAgeEnter y onNextClick
La función onAgeEnter recibe el texto del usuario, lo filtra con filterOutDigits y limita la longitud a tres caracteres, suficiente para edades realistas entre 0 y 999.
La función onNextClick corre dentro de viewModelScope.launch y valida si la edad puede convertirse a entero usando toIntOrNull(). Si el resultado es nulo o vacío, emite un evento ShowSnackBar con el mensaje "Error, la edad no puede ser vacía". Si la validación pasa, emite Success para autorizar la navegación.
¿Por qué validar en el ViewModel y no en la vista? Porque la vista debe ser pasiva. Validar en el ViewModel cumple el principio de responsabilidad única de SOLID y permite escribir pruebas unitarias sin depender de la UI.
Cómo conectar el ViewModel con AgeScreen y LaunchedEffect
Dentro de AgeScreen se reciben dos parámetros clave: el snackBarHostState y el ageViewModel [07:40]. Para escuchar los eventos del ViewModel en tiempo real se usa un LaunchedEffect con clave booleana true, que recolecta el flujo y reacciona según el tipo de evento.
kotlin LaunchedEffect(true) { viewModel.uiEvent.collect { event -> when (event) { is UiEvent.Success -> onNextClick() is UiEvent.ShowSnackBar -> snackBarHostState.showSnackbar( message = event.message.asString(context) ) } } }
El when debe ser exhaustivo, así que se añade else -> Unit para cubrir cualquier rama. El campo de texto delega su onValueChange directamente al ViewModel usando una lambda referenciada, una forma más limpia de pasar funciones:
kotlin onValueChange = ageViewModel::onAgeEnter
Lo mismo aplica al botón de continuar, que invoca ageViewModel::onNextClick. Con esto el componente queda completamente controlado desde el ViewModel.
Qué características debe manejar un ViewModel de caja de texto
Al diseñar un ViewModel para un campo de texto, conviene cubrir estas responsabilidades:
- Estado del valor actual del campo.
- Estado de error y mensaje asociado.
- Lógica de formateo y filtrado de caracteres.
- Acción de continuar o enviar.
¿Qué es una lambda referenciada en Kotlin? Es una forma compacta de pasar una función como parámetro usando
::nombreFuncion. Reemplaza bloques tipo{ valor -> objeto.funcion(valor) }y mejora la legibilidad.
Al probar la aplicación en el emulador, dejar el campo vacío y pulsar next dispara el snack bar con el mensaje de error. Al ingresar un valor válido como 36, la app navega a la pantalla de altura sin problemas. El módulo de onboarding aún tiene pantallas pendientes sin ViewModel: ese es tu reto. ¿Cuál vas a implementar primero? Cuéntame en los comentarios.