MVVM View: UI Inteligente pero Pasiva en Android
Índice de contenidos
🎭 Teoría: El Rol de la Vista
En MVVM, la View (Activity, Fragment o Composable) tiene una única responsabilidad: Reflejar el estado del ViewModel.
No toma decisiones. No calcula nada. No sabe de dónde vienen los datos. Es un espejo.
Principio de Pasividad
Una View ideal debería ser tan “tonta” que si le pasas un estado con texto “Error 404”, lo muestre felizmente en verde si así está estilado, sin cuestionar si tiene sentido.
🔄 Unidirectional Data Flow (UDF)
El concepto clave para una View moderna es el flujo unidireccional.
- State (Down): El estado fluye hacia abajo, desde el ViewModel a la View.
- Events (Up): Los eventos fluyen hacia arriba, desde la View al ViewModel.
ViewModel
│ ▲
State │ │ Events (clicks)
▼ │
View
Por qué UDF es superior
- Predecibilidad: Solo hay una forma de cambiar la UI (cambiando el estado en el VM).
- Debuggability: Si la UI está mal, sabes que el error está en el estado emitido, no en un método
setText()perdido en un listener.
📱 Implementación: Compose vs XML
En Jetpack Compose (La Era Moderna)
Compose fue diseñado pensando en UDF.
@Composable
fun LoginScreen(
// 1. State Hoisting: Recibe estado y lambdas
uiState: LoginUiState,
onLoginClick: (String, String) -> Unit
) {
Column {
TextField(
value = uiState.username,
// 2. No actualiza su propia variable. Manda evento.
onValueChange = { /* Evento al VM (opcional) */ }
)
Button(
// 3. Evento puro hacia arriba
onClick = { onLoginClick(uiState.username, "pass") }
) {
Text("Entrar")
}
if (uiState.isLoading) {
CircularProgressIndicator()
}
}
}
En XML / Views (El Legado)
En el sistema clásico, debemos forzar este comportamiento observando flujos.
class LoginFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Setup Listeners (Events Up)
binding.loginButton.setOnClickListener {
viewModel.onLoginClicked(binding.username.text.toString())
}
// Setup Observers (State Down)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
render(state)
}
}
}
}
// Función pura de renderizado
private fun render(state: LoginUiState) {
binding.progressBar.isVisible = state.isLoading
binding.loginButton.isEnabled = !state.isLoading
if (state.error != null) {
showError(state.error)
}
}
}
⚠️ Anti-Patrones Comunes en la View
1. Lógica Condicional en la UI
❌ Mal:
if (user.age > 18) {
showBeerIcon()
}
Esto es lógica de negocio. Si mañana la edad legal cambia a 21, tienes que buscar en todos los XML/Composables.
✅ Bien:
// ViewModel
val showBeer: Boolean = user.age > 18
// View
if (state.showBeer) {
showBeerIcon()
}
2. ViewModel manipulando la View
❌ Mal: Pasar la View al ViewModel.
viewModel.doSomething(binding.myTextView)
Esto destruye MVVM y crea memory leaks. El VM nunca debe importar android.view.*.
3. Crear Estado Local Duplicado
Si tienes un var isChecked en tu Composable y también un isChecked en tu ViewModel, tienes dos fuentes de verdad. Eventualmente se desincronizarán. Usa siempre el estado del ViewModel como única fuente de verdad (Single Source of Truth).
🎯 Conclusión
La View es la cara de tu aplicación, pero no debe ser el cerebro. Mantén tus vistas pasivas, reactivas y tontas. Si sigues estrictamente el Flujo Unidireccional de Datos, verás que bugs complejos de “estado inconsistente” desaparecen mágicamente.
Artículos relacionados
MVVM ViewModel: El Cerebro de la Operación
Profundiza en el componente ViewModel: Gestión de estado, ciclo de vida, corrutinas y cómo evitar los errores más comunes de diseño.
MVVM Model: La Capa de Datos Invisible pero Vital
El 'Model' en MVVM es mucho más que clases de datos. Aprende a diseñar una capa de modelo robusta que sobreviva a cambios de UI y backend.
Arquitectura MVVM en Android: Guía Completa desde Cero
Domina el patrón Model-View-ViewModel desde los conceptos básicos hasta implementaciones avanzadas con ejemplos prácticos de un juego de Buscaminas para Android.