Build a Flutter App using MVVM

⚡ Victor Ahmad 💙 ⚡
5 min readJan 19, 2023

--

Implementing Model-View-ViewModel (MVVM) in a Flutter App: A Step-by-Step Guide

MVVM

Model-View-ViewModel (MVVM) is a popular architectural pattern for building user interfaces, especially in the world of mobile app development. In this article, we’ll take a look at how to implement MVVM in a Flutter app.

The Model-View-ViewModel (MVVM) pattern separates the user interface of an app into three distinct components: the Model, the View, and the ViewModel. Each element plays a specific role in the overall architecture of the app.

Model

The Model represents the data and business logic of the app. It is responsible for storing, retrieving, and manipulating the data that the app needs to function. The Model should be completely independent of the View and ViewModel, and should only contain logic related to the data itself.

View

The View represents the user interface of the app. It is responsible for displaying the data to the user and capturing user input. The View should be completely independent of the Model and ViewModel and should only contain logic related to the presentation of the data.

ViewModel

The ViewModel acts as a bridge between the Model and the View. It provides a clean and convenient way for the View to access and manipulate the Model. The ViewModel should be completely independent of the View and should only contain logic related to the transformation of the Model data for the View.

The Model is the source of truth, it’s the representation of the data and business logic, it’s the one that should be tested. The View is the presentation of the data, it’s the one that should be styled. The ViewModel is the one that connects the Model and the View, and it’s the one that should be reused.

In MVVM, the View subscribes to changes in the ViewModel, which in turn updates the View when the Model changes. This separation of concerns allows for a more testable and maintainable codebase, as the Model, View, and ViewModel can be developed and tested independently.

In addition to the separation of concerns, MVVM also promotes a loosely coupled architecture, which allows for greater flexibility and reusability of code. With MVVM, changes to the Model or ViewModel do not affect the View, and changes to the View do not affect the Model or ViewModel. This makes it easy to make changes to one component without affecting the others, and also allows for the use of different Views for the same Model and ViewModel, for example using the same ViewModel for different platform (iOS, Android, Web) or for different screen sizes.

Another advantage of MVVM is that it makes it easier to test the different components of the app. Since the Model, View, and ViewModel are separated, they can be tested independently. This means that the Model can be tested with unit tests, while the View and ViewModel can be tested with integration tests. Additionally, the ViewModel can be tested with mock objects, which allows for testing the logic of the ViewModel without the need for a real View.

In summary, MVVM is a powerful architectural pattern that can help you build more maintainable and testable user interfaces. By separating the Model, View, and ViewModel into distinct components, you can create a clear separation of concerns in your code, making it easier to understand, test, and evolve over time. The loosely coupled architecture of MVVM also allows for greater flexibility and reusability of code and the ability to test each component independently.

Let’s start by creating a simple example app that displays a list of items. Our Model will be a simple list of strings, and our View will be a ListView that displays these items.

First, we’ll create a new Flutter project and add a new file called model.dart. In this file, we'll define our Model class, which will be a simple list of strings:

class ItemList {
List<String> items = ['Item 1', 'Item 2', 'Item 3'];
}

Next, we’ll create a new file called viewmodel.dart, where we'll define our ViewModel class. This class will act as a bridge between the Model and the View, and will provide a convenient way for the View to access and manipulate the Model.

class ItemListViewModel {
ItemList _itemList;

ItemListViewModel(this._itemList);

List<String> get items => _itemList.items;

void addItem(String item) {
_itemList.items.add(item);
}
}

In this example, the ViewModel class has a reference to the Model (_itemList) and provides two methods for the View to access: items, which returns the list of items, and addItem, which adds a new item to the list.

Finally, we’ll create the View in our main.dart file. We'll create a new StatefulWidget called HomePage, which will display the list of items in a ListView:

class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
ItemListViewModel _viewModel;

@override
void initState() {
super.initState();
_viewModel = ItemListViewModel(ItemList());
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('MVVM Example')),
body: ListView.builder(
itemCount: _viewModel.items.length,
itemBuilder: (context, index) {
return ListTile(title: Text(_viewModel.items[index]));
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_viewModel.addItem('New Item');
setState(() {});
},
child: Icon(Icons.add),
),
);
}
}

In this example, the HomePage widget creates a new instance of the ItemListViewModel in its initState method and uses it to access the list of items in its build method. The ListView.builder widget is used to display the items in a scrollable list, and a FloatingActionButton is used to allow the user to add new items to the list.

When the user taps the FloatingActionButton, the addItem method on the _viewModel is called, which adds a new item to the list. The setState method is then called to trigger a rebuild of the widget, which updates the list displayed on the screen.

It’s important to note that in this example, the HomePage widget is tightly coupled to the ItemListViewModel class, meaning that if we wanted to change the Model or the ViewModel, we would need to make changes to the HomePage widget as well. To avoid this tight coupling and make our code more flexible and reusable, we can use dependency injection to provide the HomePage widget with an instance of the ItemListViewModel class. This way, we can easily swap out the Model or ViewModel without affecting the View.

In conclusion, MVVM is a powerful architectural pattern that can help you build more maintainable and testable user interfaces. By separating the Model, View, and ViewModel into distinct components, you can create a clear separation of concerns in your code, making it easier to understand, test, and evolve over time. With the example provided you should have a good understanding of how to implement MVVM in a Flutter app and use it to build more robust and scalable user interfaces.

Apps built using Flutter with MVVM

--

--