How to Implement MVC in Flutter

Using Model View Controller in Flutter heyylateef | Jul 25 2023

Overview

Here at LateefLab, we've been traditionally a web developer blog. While we're still keeping that focus but let us introduce Flutter, a cross-platform framework written in Dart that can be used to create applications for web, iOS, Android, Windows, Mac, and Linux.  Here at the Lab, we love Flutter because it allows developers to quickly build applications for nearly every modern platform. While Django may still be our go to solution for web applications, Flutter is our preferred framework for building mobile applications. 


Flutter is a UI framework that allows us to build components (called Widgets). Similar to React and Angular for web frameworks, Flutter's use-case for us would be to build user interfaces. Since we're using it to build a mobile UI, we can still use Django as a REST API to supply some data to the UI layer. 


One major reason we love using Django is because it applies the "Model-View-Controller"(MVC) design pattern. The MVC design pattern have plenty advantages for both solo developers and teams:

  • Consistent folder/file structure
  • Teammates can quickly get involved in project
  • Clear separation of features within application

Lets try to apply the Model-View-Controller design pattern to a Flutter project for building a mobile application!


Understanding MVC Design Pattern

The Model-View-Controller (MVC) pattern is an architectural pattern commonly used in software development. It separates the application into three main components:

 

Model: The Model represents the application's data and business logic. It encapsulates data-related operations, such as fetching data from databases or APIs and performing calculations. It doesn't have any knowledge of the user interface (UI).

 

View: The View is responsible for rendering the user interface and presenting data to the user. It receives user input and forwards it to the Controller. In Flutter, the View is typically implemented using widgets.

 

Controller: The Controller acts as an intermediary between the Model and the View. It receives user input from the View and manipulates the Model accordingly. It also updates the View with the latest data from the Model.

 

By separating these components, MVC promotes the principle of "separation of concerns," making the codebase easier to manage, test, and maintain.


To implement the MVC pattern in Flutter, follow these steps:


Project Folder Structure

Folder structure is very important when implementing a design pattern such as MVC. Here is the folder structure that some Flutter developers use. You should create the same folders in your project.


.
└── lib/
    ├── controllers/
    │   └── controller.dart
    ├── models/
    │   └── models.dart
    ├── views/
    │   ├── widgets/
    │   │   ├── widgets.dart
    │   │   └── ...
    │   └── screens/
    │       ├── somescreen.dart
    │       └── ...
    └── main.dart






1. Model

Define your data models in separate classes. These models should represent the data structures that your application will use. For example, if you're building a simple task management app, you might have a Task class as a model. Inside of the models folder, make a file named models.dart, copy and paste the following:

class Task {
  final int id;
  final String title;
  final bool isCompleted;

  Task({required this.id, required this.title, this.isCompleted = false});
}



2. Controller

Create Controller classes that will handle business logic and act as a bridge between the Model and the View. Controllers will have methods to handle user actions and update the Model accordingly. Inside of the controllers folder, make a file named controllers.dart, copy and paste the following:

class TaskController {
  List<Task> _tasks = [];

  List<Task> get tasks => _tasks;

  void addTask(Task task) {
    _tasks.add(task);
    // Notify the View that the data has changed
    // (using Flutter's setState() or Provider, for example)
  }

  void completeTask(int taskId) {
    _tasks.firstWhere((task) => task.id == taskId).isCompleted = true;
    // Notify the View that the data has changed
  }
}


3. View

Create Flutter widgets that represent the user interface. Widgets should be responsible for presenting data and receiving user input. They should not contain any business logic. Inside of the views/widgets folder, make a file named widgets.dart, copy and paste the following:

class TaskListView extends StatelessWidget {
  final TaskController controller;

  TaskListView(this.controller);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: controller.tasks.length,
      itemBuilder: (context, index) {
        final task = controller.tasks[index];
        return ListTile(
          title: Text(task.title),
          leading: Checkbox(
            value: task.isCompleted,
            onChanged: (newValue) {
              controller.completeTask(task.id);
            },
          ),
        );
      },
    );
  }
}



4. Integrate the Components in Main Component

In the main.dart file, create instances of your Model and Controller classes. Then, pass the Controller instance to the View widgets.

void main() {
  final taskController = TaskController();
  runApp(MyApp(taskController));
}

class MyApp extends StatelessWidget {
  final TaskController taskController;

  MyApp(this.taskController);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Task Management App')),
        body: TaskListView(taskController),
      ),
    );
  }
}


About The Lab

Like the content posted on this site? Need help with another web project? Want to give some feedback? Feel free to contact me via email or social media!

Know more!
DigitalOcean Referral Badge