How to create a Modular Application with Flutter — Part 1

Alvaro Armijos
5 min readApr 7, 2024

Introduction

In the rapidly evolving realm of Flutter development, optimizing the development workflow and enhancing code reusability are vital for creating robust and scalable applications. One effective strategy that has garnered considerable attention is leveraging the use of monorepos (monorepositories). But what exactly are monorepos, and how can they transform your Flutter projects?

A monorepo is a single repository that consolidates multiple interconnected projects or modules, housing all the code for an application within a single location. This method presents various benefits, including improved code organization, enhanced component reusability, and streamlined dependency management. Nonetheless, there are certain factors to consider, such as the heightened complexity during the initial setup phase and the necessity to keep a solid structure as the project grows.

In this series of articles, we will explore the world of monorepos. We will conduct an in-depth analysis of the Star Wars Characters App project, a simple example, to illustrate how we can apply the Android recommended principles of Modularization by Layers and Features to modularize the application.

Furthermore, we will explore the utilization of Melos, a powerful tool for managing monorepos. We will also discuss how to implement Dependency Injection across different modules within the project. Lastly, we will cover the process of generating code coverage for the entire project.

Application

To get a practical view, we take the Star Wars Characters App as an example.

The project structure is as follows:

app/
app
packages/
core/
utility
ui
data/
catalog
device
features/
home

Here we have divided the project into four layers. The first layer is the root project which will hold common configurations that applies to all the different packages in the project. The 2nd layer is having independent feature packages that do not depend on each other. The 3rd layer is te domain of the application, contains the main functionality and usually contains a repository, data sources and entity classes. The 4th layer consists of utility packages that are used in multiple packages.

Modularization

Modular programming is a methodology that aims to divide a software system into independent and reusable modules. Each module is designed to focus on a specific functionality and is developed in isolation, which makes the code easier to understand, maintain, and scale. In the context of Flutter, modularization enables us to organize the project into layers and features, thereby separating responsibilities and promoting code reuse.

Create Modules in Flutter

  1. To create a local package for the flutter project you can create a separate directory called “packages” inside the root directory of the main flutter project.
  2. Run the following command:
flutter create --template=package packages/new_package_name

After this is done, we have a new module in our project, this way it is very easy to create all the modules we need.

The problem

When dealing with multiple packages in a project, developers often encounter challenges like managing dependencies, maintaining consistent linting and formatting standards across packages, executing tests for each package, and handling tasks such as build_runner and code coverage merging. These challenges highlight the need for an efficient solution to streamline the development process.

One strategy is to create a script to automate these tasks, which can save time but requires additional setup effort.

To meet this challenge, we can harness the power of Melos.

Melos

To install Melos, run the following command in your terminal:

dart pub global activate melos

The project structure should look like the following:

Project structure

To configure Melos, we need to create a top level melos.yaml file. The structure looks like this now:

# The name of the project (required) is used for displaying purposes within IO environments and IDEs.
name: star_wars

# A list of paths to local packages that are included in the Melos workspace. Each entry can be a specific path or a glob pattern.
packages:
- packages/** #take all the modules inside the path packages/
- . #take the root application inside lib/app directory

# Recommended option for projects with Dart 2.17.0 or greater.
#
# This enables a new mechanism for linking local packages, which integrates
# better with other tooling (e.g. dart tool, flutter tool, IDE plugins) than the
# mechanism currently being used by default. Please read the documentation for
# usePubspecOverrides before enabling this feature.
#
# See https://melos.invertase.dev/getting-started#setup
command:
bootstrap:
usePubspecOverrides: true

Let’s understand the above script:

  • name: You have to give the name of your project. You can find it inside the pubspec.yaml of the root project.
  • packages: This list should contain paths to the individual packages within your project. Each path can be defined using the glob pattern expansion format.

Melos Bootstrap

Now run the following command in your terminal from your root project to link all your local packages together.

melos bootstrap 
or
melos bs

Bootstrapping has 2 primary roles:

  1. Installing all package dependencies (internally using pub get).
  2. Locally linking any packages together.

You can read more about bootstrap Melos here.

Melos Clean

You can execute this command when you want to remove temporary files from your project(build artifacts, pub files etc). This is how the command looks like:

melos clean

Commands

In the melos.yaml we can also define our commands which are executed in every Dart/Flutter package inside our defined Melos workspace.

scripts:
all:
run: melos run sync && melos run generate
description: Run sync and generate scripts.
sync:
run: melos exec -- "flutter clean" && melos exec -- "flutter pub get"
description: Run `flutter clean && flutter pub get` in all packages
generate:
run: melos exec -- "dart run build_runner build --delete-conflicting-outputs"
description: Run `dart run build_runner build` in all packages

We are now able to execute our script with melos SCRIPT_NAME. To run the sync and generate command in all packages we can use this command:

melos all

You can add in the melos.yaml file every script you want, like running the build_runner. Check also the Melos documentation to find more about the scripts configuration.

Up to this point, we have covered modularizing our project following Android’s recommendations, creating modules within an application, and managing them effectively using Melos.

In subsequent articles in this series, we will discuss how to implement Dependency Injection across different modules within the project, and we will cover the process of generating code coverage for the entire project.

If you like it, you can Buy Me A Coffee!

--

--

Alvaro Armijos

Electronic and Telecommunications Engineer | #Flutter Developer 💙 | Always eager to learn | https://www.linkedin.com/in/alvaro-armijos-sarango/