MVC vs. MVVM Architecture in Flutter: A Deep Dive with a To-Do List Example
Chances are, you’ve stumbled upon terms like MVC and MVVM when diving into app development. At first glance, these architectural patterns can feel overwhelming — especially if you’re new to the scene. But don’t worry, I’m here to break them down and make them easier to grasp.
Understanding MVC (Model-View-Controller) in Flutter
What is MVC?
MVC stands for Model-View-Controller. This design pattern/architecture divides your application into three main components:
Model: Manages the data and business logic.
View: Displays the data to the user and sends user input to the Controller.
Controller: Acts as the mediator between the Model and the View. It updates the Model based on user interactions and refreshes the View accordingly.
Though Flutter doesn’t directly follow the MVC pattern, you can implement it by manually creating separate classes for the Model, View, and Controller.
To-Do List Example in MVC
Model
The ToDoItem class represents each to-do item in the list:
class ToDoItem {
String title;
bool isCompleted;
ToDoItem({required this.title, this.isCompleted = false});
void toggleCompletion() {
isCompleted = !isCompleted;
}
}
Here we have created a class called ToDoItem which has 2 variables and a public function that toggles isCompleted variable.
Controller
The ToDoController manages the list of to-do items and interacts with the ToDoItem model:
class ToDoController {
List<ToDoItem> toDoList = [];
void addItem(String title) {
toDoList.add(ToDoItem(title: title));
}
void toggleItemCompletion(int index) {
toDoList[index].toggleCompletion();
}
void deleteItem(int index) {
toDoList.removeAt(index);
}
}
We have created another class called ToDoController which holds all the functions to manipulate our toDoList.
View
The View in Flutter is simply the widget that displays the data. It listens to the controller for changes.
class ToDoView extends StatefulWidget {
@override
_ToDoViewState createState() => _ToDoViewState();
}
class _ToDoViewState extends State<ToDoView> {
late ToDoController _controller;
@override
void initState() {
super.initState();
_controller = ToDoController(); // Initialize the controller in initState
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('MVC To-Do List'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: InputDecoration(
labelText: 'Add To-Do Item',
),
onSubmitted: (value) {
setState(() {
_controller.addItem(value);
});
},
),
),
Expanded(
child: ListView.builder(
itemCount: _controller.toDoList.length,
itemBuilder: (context, index) {
final item = _controller.toDoList[index];
return ListTile(
title: Text(
item.title,
style: TextStyle(
decoration: item.isCompleted
? TextDecoration.lineThrough
: TextDecoration.none,
),
),
leading: Checkbox(
value: item.isCompleted,
onChanged: (value) {
setState(() {
_controller.toggleItemCompletion(index);
});
},
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
setState(() {
_controller.deleteItem(index);
});
},
),
);
},
),
),
],
),
);
}
}
Here we are listening to ToDoController controller to display toDoList and appending ToDoListItem. We are also using an inbuilt flutter function called setState((){}) to reflect changes to the UI.
Understanding MVVM (Model-View-ViewModel) in Flutter
What is MVVM?
MVVM stands for Model-View-ViewModel. It’s a refinement of MVC where a ViewModel replaces the Controller. The ViewModel is responsible for managing the state and acts as a bridge between the Model and the View. It allows for a clear separation of concerns and makes testing and maintenance easier.
Model: Manages the data.
View: Displays the data (widgets in Flutter).
ViewModel: Holds the state of the UI, handles business logic, and interacts with the Model.
In Flutter, MVVM can be implemented using state management tools like ChangeNotifier, Provider, or Riverpod. We will use ChangeNotifier & Provider for this example.
To-Do List Example in MVVM
Model
The ToDoItem model is the same as in the MVC example.
class ToDoItem {
String title;
bool isCompleted;
ToDoItem({required this.title, this.isCompleted = false});
void toggleCompletion() {
isCompleted = !isCompleted;
}
}
ViewModel
The ToDoViewModel manages the state and business logic. It extends ChangeNotifier to notify the UI when data changes.
import 'package:flutter/material.dart';
import 'model.dart';
class ToDoViewModel extends ChangeNotifier {
List<ToDoItem> toDoList = [];
void addItem(String title) {
toDoList.add(ToDoItem(title: title));
notifyListeners(); // This reflects the changes to the UI
}
void toggleItemCompletion(int index) {
toDoList[index].toggleCompletion();
notifyListeners();
}
void deleteItem(int index) {
toDoList.removeAt(index);
notifyListeners();
}
}
View
The View listens to changes in the ViewModel using ChangeNotifier Provider and updates the UI accordingly.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'viewmodel.dart';
class ToDoView extends StatelessWidget {
final TextEditingController _textController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('MVVM To-Do List'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _textController,
decoration: InputDecoration(
labelText: 'Add To-Do Item',
),
onSubmitted: (value) {
Provider.of<ToDoViewModel>(context, listen: false).addItem(value);
_textController.clear();
},
),
),
Expanded(
child: Consumer<ToDoViewModel>( // Spits out changes in a stream
builder: (context, viewModel, child) {
return ListView.builder(
itemCount: viewModel.toDoList.length,
itemBuilder: (context, index) {
final item = viewModel.toDoList[index];
return ListTile(
title: Text(
item.title,
style: TextStyle(
decoration: item.isCompleted
? TextDecoration.lineThrough
: TextDecoration.none,
),
),
leading: Checkbox(
value: item.isCompleted,
onChanged: (value) {
viewModel.toggleItemCompletion(index);
},
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
viewModel.deleteItem(index);
},
),
);
},
);
},
),
),
],
),
);
}
}
Conclusion
Personally, I prefer the MVVM architecture because it offers a more robust and flexible approach to state management. It doesn’t constrain your app’s growth and ensures a clear separation of business logic from UI code — a crucial factor for maintainability and scalability as your application evolves
Keep Fluttering……