Flutter is a technology developed by Google that allows you to create applications for Android, iOS, Web, Windows, macOS, and Linux using a single codebase. One of the peculiarities of Flutter is that it does not impose a specific architecture for organizing applications, unlike other frameworks that often recommend patterns like MVC.
When I started working with Flutter, one of the first important decisions was how to structure the project so that the code was scalable and easy to maintain. One of the architectures that works best in this ecosystem is MVVM (Model-View-ViewModel).
In this article, we are going to look at what MVVM is, how it works in Flutter, and how to implement it using Provider for state management.
What is MVVM and why use it in Flutter
MVVM is an architectural pattern that separates the application into three main components:
- Model
- View
- ViewModel
The main objective is to separate business logic from the user interface, allowing each part to have a clear responsibility.
In Flutter, this is especially useful because interfaces are formed by reactive widgets, and separating the logic allows you to keep the code organized even as the application grows.
In my case, when I started developing larger applications in Flutter, I noticed that mixing business logic inside the widgets made the code difficult to maintain. Implementing MVVM helped to keep the UI clean and move all the logic to the ViewModel.
MVVM pattern components
Model
The Model represents the application data.
It is responsible for:
- Fetching data from an API
- Accessing databases
- Converting JSON responses into objects
Simple model example:
class Item { final String title; final String description; Item({required this.title, required this.description});}The model knows nothing about the user interface, it only represents data.
View
The View is the presentation layer, that is, the interface the user sees.
In Flutter, this corresponds to the widgets.
The view:
- Displays data
- Listens for changes in the ViewModel
- Sends user events
An important principle is that the view should not contain complex business logic.
ViewModel
The ViewModel acts as an intermediary between the view and the model.
Its responsibilities include:
- Managing state
- Processing user actions
- Requesting data from the model
- Notifying the interface of changes
In Flutter, a common way to implement a ViewModel is by using ChangeNotifier.
class ItemViewModel extends ChangeNotifier { List<Item> _items = []; List<Item> get items => _items; void fetchItems() { // logic to fetch data notifyListeners(); }}Every time the state changes, notifyListeners() is called, which causes the interface to update.
Benefits of using MVVM in Flutter
Implementing MVVM in Flutter offers several important advantages.
1. Clear separation of responsibilities
Business logic is moved to the ViewModel and the interface stays clean.
This makes widgets focus solely on displaying data and reacting to events.
2. Easier to maintain code
When the application grows, separating responsibilities allows you to modify parts of the code without affecting other layers.
3. Better unit tests
By moving the logic to the ViewModel, it is possible to test the behavior without depending on the interface.
This facilitates the creation of automated tests.
4. Logic reuse
A ViewModel can be reused by different views, avoiding code duplication.
Folder structure for an MVVM architecture in Flutter
Good project organization is key to keeping the architecture clear.
A common structure could be:
lib/│├── network/│ └── api_client.dart│├── models/│ └── item.dart│├── repository/│ └── item_repository.dart│├── view/│ └── item_list_view.dart│├── viewmodel/│ └── item_viewmodel.dart│├── utils/│└── res/What each folder does
network
Contains the classes in charge of making HTTP calls or accessing databases.
models
Defines the data structures used in the application.
repository
Acts as an intermediary between the ViewModel and the data sources.
view
Contains the widgets and screens.
viewmodel
Manages state and business logic.
This structure helps maintain a clear separation between layers.
How the data flow works in MVVM
The data flow in MVVM follows this process:
- The user interacts with the View.
- The view sends the event to the ViewModel.
- The ViewModel requests data from the Model or Repository.
- The Model returns the data.
- The ViewModel updates the state.
- The view updates automatically.
This flow makes the interface reactive and decoupled from logic.
Implementing MVVM in Flutter with Provider
A simple way to implement MVVM in Flutter is using Provider as a state manager.
Provider allows widgets to listen for changes in the ViewModel.
Create the data model
class Item { final String title; final String description; Item({required this.title, required this.description});}Create the ViewModel with ChangeNotifier
class ItemViewModel extends ChangeNotifier { List<Item> _items = []; List<Item> get items => _items; void fetchItems() { // Data simulation _items = [ Item(title: "Item 1", description: "Description 1"), Item(title: "Item 2", description: "Description 2"), ]; notifyListeners(); }}Connect the view with Provider
void main() { runApp( ChangeNotifierProvider( create: (context) => ItemViewModel(), child: MaterialApp( home: ItemListView(), ), ), );}Create the view
class ItemListView extends StatelessWidget { @override Widget build(BuildContext context) { final itemViewModel = Provider.of<ItemViewModel>(context); return Scaffold( appBar: AppBar(title: Text('Items list')), 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), ); }, ), ); }}Best practices when using MVVM in Flutter
To maintain a clean architecture:
- Keep widgets free of business logic
- Use repositories to access data
- Keep ViewModels small and specific
- Separate domain models from API models
- Avoid direct dependencies between View and Model
Common mistakes when implementing MVVM in Flutter
When implementing this pattern, it is common to make some mistakes.
Mixing logic in widgets
This breaks the separation of responsibilities.
Creating overly large ViewModels
Each ViewModel should have a single responsibility.
Not separating the repository layer
Without repositories, the ViewModel ends up accessing the API directly.
MVVM vs other architectures in Flutter
MVVM vs MVC
In MVC, the controller handles both logic and interaction with the view.
In MVVM, the ViewModel acts as an intermediary and keeps the view decoupled.
MVVM vs Clean Architecture
Clean Architecture is more robust and scalable, but also more complex.
MVVM is usually easier to implement in small or medium projects.
Conclusion
Flutter offers a lot of freedom when it comes to structuring applications, and choosing a good architecture is key to keeping the project organized.
The MVVM pattern in Flutter allows you to separate the user interface from the business logic, facilitating maintenance, code reuse, and testing.
Combined with tools like Provider, it is possible to implement a clean and scalable architecture without adding too much complexity to the project.
FAQ
Does Flutter use MVVM?
Flutter does not impose a specific architecture, but MVVM is one of the most used patterns.
What is the best state manager for MVVM in Flutter?
The most used are:
- Provider
- Riverpod
- Bloc
Is MVVM better than MVC in Flutter?
In many cases yes, because it separates business logic from the interface better.