Flutter es una tecnología desarrollada por Google que permite crear aplicaciones para Android, iOS, Web, Windows, macOS y Linux usando una sola base de código. Una de las particularidades de Flutter es que no impone una arquitectura específica para organizar las aplicaciones, a diferencia de otros frameworks que suelen recomendar patrones como MVC.
Cuando empecé a trabajar con Flutter, una de las primeras decisiones importantes fue cómo estructurar el proyecto para que el código fuera escalable y fácil de mantener. Una de las arquitecturas que mejor funciona en este ecosistema es MVVM (Model-View-ViewModel).
En este artículo vamos a ver qué es MVVM, cómo funciona en Flutter y cómo implementarlo usando Provider para la gestión de estado.
Qué es MVVM y por qué usarlo en Flutter
MVVM es un patrón de arquitectura que separa la aplicación en tres componentes principales:
- Model
- View
- ViewModel
El objetivo principal es separar la lógica de negocio de la interfaz de usuario, permitiendo que cada parte tenga una responsabilidad clara.
En Flutter esto es especialmente útil porque las interfaces están formadas por widgets reactivos, y separar la lógica permite mantener el código organizado incluso cuando la aplicación crece.
En mi caso, cuando empecé a desarrollar aplicaciones más grandes en Flutter, noté que mezclar lógica de negocio dentro de los widgets hacía que el código fuera difícil de mantener. Implementar MVVM ayudó a mantener la UI limpia y mover toda la lógica al ViewModel.
Componentes del patrón MVVM
Model
El Model representa los datos de la aplicación.
Se encarga de:
- Obtener datos desde una API
- Acceder a bases de datos
- Convertir respuestas JSON en objetos
Ejemplo simple de modelo:
class Item {
final String title;
final String description;
Item({required this.title, required this.description});
}El modelo no conoce nada de la interfaz de usuario, solo representa datos.
View
La View es la capa de presentación, es decir, la interfaz que ve el usuario.
En Flutter, esto corresponde a los widgets.
La vista:
- Muestra datos
- Escucha cambios en el ViewModel
- Envía eventos del usuario
Un principio importante es que la vista no debería contener lógica de negocio compleja.
ViewModel
El ViewModel actúa como intermediario entre la vista y el modelo.
Sus responsabilidades incluyen:
- Gestionar el estado
- Procesar acciones del usuario
- Solicitar datos al modelo
- Notificar cambios a la interfaz
En Flutter, una forma común de implementar un ViewModel es usando ChangeNotifier.
class ItemViewModel extends ChangeNotifier {
List<Item> _items = [];
List<Item> get items => _items;
void fetchItems() {
// lógica para obtener datos
notifyListeners();
}
}Cada vez que el estado cambia, se llama a notifyListeners(), lo que provoca que la interfaz se actualice.
Beneficios de usar MVVM en Flutter
Implementar MVVM en Flutter ofrece varias ventajas importantes.
1. Separación clara de responsabilidades
La lógica de negocio se mueve al ViewModel y la interfaz se mantiene limpia.
Esto hace que los widgets se enfoquen únicamente en mostrar datos y reaccionar a eventos.
2. Código más fácil de mantener
Cuando la aplicación crece, separar responsabilidades permite modificar partes del código sin afectar otras capas.
3. Mejores pruebas unitarias
Al mover la lógica al ViewModel, es posible probar el comportamiento sin depender de la interfaz.
Esto facilita la creación de tests automatizados.
4. Reutilización de lógica
Un ViewModel puede ser reutilizado por diferentes vistas, evitando duplicación de código.
Estructura de carpetas para una arquitectura MVVM en Flutter
Una buena organización del proyecto es clave para mantener la arquitectura clara.
Una estructura común podría ser:
lib/
│
├── network/
│ └── api_client.dart
│
├── models/
│ └── item.dart
│
├── repository/
│ └── item_repository.dart
│
├── view/
│ └── item_list_view.dart
│
├── viewmodel/
│ └── item_viewmodel.dart
│
├── utils/
│
└── res/Qué hace cada carpeta
network
Contiene las clases encargadas de realizar llamadas HTTP o acceder a bases de datos.
models
Define las estructuras de datos usadas en la aplicación.
repository
Actúa como intermediario entre el ViewModel y las fuentes de datos.
view
Contiene los widgets y pantallas.
viewmodel
Gestiona el estado y la lógica de negocio.
Esta estructura ayuda a mantener una separación clara entre capas.
Cómo funciona el flujo de datos en MVVM
El flujo de datos en MVVM sigue este proceso:
- El usuario interactúa con la View.
- La vista envía el evento al ViewModel.
- El ViewModel solicita datos al Model o Repository.
- El Model devuelve los datos.
- El ViewModel actualiza el estado.
- La vista se actualiza automáticamente.
Este flujo hace que la interfaz sea reactiva y desacoplada de la lógica.
Implementación de MVVM en Flutter con Provider
Una forma sencilla de implementar MVVM en Flutter es usando Provider como gestor de estado.
Provider permite que los widgets escuchen cambios en el ViewModel.
Crear el modelo de datos
class Item {
final String title;
final String description;
Item({required this.title, required this.description});
}Crear el ViewModel con ChangeNotifier
class ItemViewModel extends ChangeNotifier {
List<Item> _items = [];
List<Item> get items => _items;
void fetchItems() {
// Simulación de datos
_items = [
Item(title: "Item 1", description: "Descripción 1"),
Item(title: "Item 2", description: "Descripción 2"),
];
notifyListeners();
}
}Conectar la vista con Provider
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => ItemViewModel(),
child: MaterialApp(
home: ItemListView(),
),
),
);
}Crear la vista
class ItemListView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final itemViewModel = Provider.of<ItemViewModel>(context);
return Scaffold(
appBar: AppBar(title: Text('Lista de elementos')),
body: ListView.builder(
itemCount: itemViewModel.items.length,
itemBuilder: (context, index) {
final item = itemViewModel.items[index];
return ListTile(
title: Text(item.title),
subtitle: Text(item.description),
);
},
),
);
}
}Buenas prácticas al usar MVVM en Flutter
Para mantener una arquitectura limpia:
- Mantener los widgets libres de lógica de negocio
- Usar repositorios para acceder a datos
- Mantener ViewModels pequeños y específicos
- Separar modelos de dominio y modelos de API
- Evitar dependencias directas entre View y Model
Errores comunes al implementar MVVM en Flutter
Al implementar este patrón es común cometer algunos errores.
Mezclar lógica en los widgets
Esto rompe la separación de responsabilidades.
Crear ViewModels demasiado grandes
Cada ViewModel debería tener una única responsabilidad.
No separar la capa de repositorio
Sin repositorios, el ViewModel termina accediendo directamente a la API.
MVVM vs otras arquitecturas en Flutter
MVVM vs MVC
En MVC, el controlador maneja tanto la lógica como la interacción con la vista.
En MVVM, el ViewModel actúa como intermediario y mantiene la vista desacoplada.
MVVM vs Clean Architecture
Clean Architecture es más robusta y escalable, pero también más compleja.
MVVM suele ser más fácil de implementar en proyectos pequeños o medianos.
Conclusión
Flutter ofrece mucha libertad a la hora de estructurar aplicaciones, y elegir una buena arquitectura es clave para mantener el proyecto organizado.
El patrón MVVM en Flutter permite separar la interfaz de usuario de la lógica de negocio, facilitando el mantenimiento, la reutilización del código y las pruebas.
Combinado con herramientas como Provider, es posible implementar una arquitectura limpia y escalable sin añadir demasiada complejidad al proyecto.
FAQ
¿Flutter usa MVVM?
Flutter no impone una arquitectura específica, pero MVVM es uno de los patrones más utilizados.
¿Cuál es el mejor gestor de estado para MVVM en Flutter?
Los más utilizados son:
- Provider
- Riverpod
- Bloc
¿MVVM es mejor que MVC en Flutter?
En muchos casos sí, porque separa mejor la lógica de negocio de la interfaz.