Depuis quelques années, les projets mobiles Android et iOS sont communément découpés en une architecture multimodulaire. Cela permet d’atomiser chaque brique technique ou fonctionnelle en un module indépendant sous forme d’une librairie. Dans cette approche, les différentes briques se situent la plupart du temps au sein du même projet. Sur Android et iOS cette approche est très facile grâce à Gradle, CocoaPods, Swift-package-manager… C’est le principe même d’une architecture multimodulaire.

Est-ce que cette architecture est abordable en Flutter ?

Tout d’abord, analysons les propriétés disponibles dans le fichier pubspec.yml : https://dart.dev/tools/pub/pubspec

La propriété dependencies permet de référencer les différents packages utilisés dans votre application par version, URL git, path : https://dart.dev/tools/pub/dependencies

Dans une architecture multimodulaire, que nous appelons plus précisément multipackage en Flutter, nous pourrions référencer un ensemble de packages situés dans notre projet de la façon suivante :

dependencies:
  first_feature:
    path: ../packages/first_feature

Nous constatons qu’il est possible de référencer un package au sein d’un même repository, et que le principe d’architecture multipackage est possible en reproduisant ce principe x fois.

Cependant, ce type d’architecture risque d’être contraignante lorsqu’un projet atteindra un nombre conséquent de packages. Pour mettre à jour vos dépendances, il faudra appliquer la ligne de commande flutter pub get à la racine de l’ensemble de vos packages. Idem, si vous souhaitez générer vos fichiers avec buid_runner, il sera nécessaire d’itérer la commande suivante dans tous vos packages :

flutter pub run build_runner build --delete-conflicting-outputs

Pour automatiser un ensemble de tâche sur vos packages, je vous conseille d’utiliser la dépendance Melos : https://melos.invertase.dev/.

La CLI melos permet de manager l’ensemble de vos packages dans une approche “monorepo”.

Des projets multipackages l’utilisent :

Pour installer Melos, lancer la commande :

dart pub global activate melos

Ensuite, créer un fichier melos.yaml à la racine de votre projet. Ce fichier représente le fichier de configuration Melos qui sera lu par la CLI. La première étape consistera par référencer l’ensemble de vos packages :

name: my_project

packages:
  - app/**
  - packages/**

La propriété packages prend un tableau permettant de référencer l’ensemble des dossiers contenant vos différents packages.

Maintenant, vous pouvez effectuer la commande melos bs. Cette commande permet d’effectuer la commande flutter pub get sur chacun des packages et de construire les fichiers de configuration pour votre IDE. Le résultat devrait être similaire à ceci :

melos bootstrap
  └> /Users/mslimani/Dev/projects/my_project_melos

Running "flutter pub get" in workspace packages...
Note: this may take a while in large workspaces such as this one.
  ✓ feature_one
    └> packages/feature_one
  ✓ feature_two
    └> packages/feature_two
  ✓ app
    └> app

Linking workspace packages...
  > SUCCESS

Generating IntelliJ IDE files...
  > SUCCESS

 -> 3 packages bootstrapped

Automatiser un ensemble de tâches ?

Le fichier de description melos.yaml va permettre d’ajouter un ensemble de scripts qui pourront être joués sur un ou plusieurs packages selon les conditions appliquées (filtre). La liste des filtres est disponible ici : https://melos.invertase.dev/filters.

Voici un exemple pour générer vos fichiers dart :

name: my_project

packages:
  - app/**
  - packages/**

scripts:
  generate:
    run: melos exec -c 1 --depends-on="build_runner" --flutter -- "flutter pub run build_runner build --delete-conflicting-outputs"
    description: Build all generated files for Flutter packages in this project.

Le script generate permet de lancer la commande de génération de code Dart sur les packages ayant la dépendance build_runner.

Pour lancer la commande generate :

melos run generate

Grâce à Melos, vous pourrez gérer votre projet multipackage très facilement. N’hésitez-pas à consulter la documentation, car l’outil regorge de fonctionnalités intéressantes comme la génération de changelog, automatisation des releases, …

Pour conclure, je vous laisse un exemple simple d’un fichier Melos pouvant être utilisé dans vos projets :

name: my_project

packages:
  - app/**
  - packages/**

scripts:

  generate:
    run: melos exec -c 1 --depends-on="build_runner" --flutter -- "flutter pub run build_runner build --delete-conflicting-outputs"
    description: Build all generated files for Flutter packages in this project.

  analyze:
    run: |
      melos exec -c 5 -- \
        dart analyze .
    description: |
      Run `dart analyze` in all packages.
       - Note: you can also rely on your IDEs Dart Analysis / Issues window.

  test:
    run: melos run test:flutter --no-select
    description: Run all Dart & Flutter tests in this project.

  test:flutter:
    run: melos exec --dir-exists="test" -c 1 -- "flutter test --coverage"
    description: Run Flutter tests for a specific package in this project.
    select-package:
      flutter: true
      dir-exists: test

  clean:
    run: flutter clean
    select-package:
      flutter: true

  clean:all:
    run: melos exec -c 1 --fail-fast -- "flutter clean"