Retour au blog
flutter

Mon architecture Flutter est incassable — même par l'IA

Par Youcef EL KAMEL
9 min de lecture

Mon architecture Flutter incassable, même par l'IA

J’ai un problème avec l’IA qui code : elle triche.

Donnez-lui un prompt, même détaillé, et elle va prendre le chemin le plus court. Importer un repository directement dans un widget. Coller de la logique métier dans un build(). Créer un fichier de 400 lignes avec trois classes dedans. Et le pire : ça compile. Ça marche. Et ça pourrit le codebase en silence.

Après 7 ans de Flutter et des dizaines de milliers de lignes maintenues en solo, j’ai construit une architecture où le compilateur Dart lui-même empêche ces raccourcis. Pas des conventions. Pas des code reviews. Des erreurs de compilation.

Ça s’appelle le Layou Dev Kit, et voici comment ça fonctionne.

Le problème : l’IA ne respecte rien

Si tu utilises Claude Code, Cursor, Copilot ou n’importe quel agent IA pour coder du Flutter, tu as forcément vécu ça :

  • L’agent importe directement un DataSource Firestore dans un widget
  • Il crée un StatefulWidget de 300 lignes qui mélange UI, état, et appels réseau
  • Il met trois classes dans le même fichier
  • Il utilise setState au lieu de Riverpod parce que c’est “plus simple”

Le résultat : du code qui tourne, mais qui est inmaintenable. Et plus le projet grossit, plus la dette s’accumule, de façon exponentielle.

Le vrai problème, c’est que les conventions documentées ne suffisent pas. L’IA les “oublie” dès que son contexte se remplit. Il faut des contraintes structurelles, des trucs que même un modèle à 200K tokens ne peut pas contourner.

L’idée : le compilateur comme garde-fou

La Clean Architecture, tout le monde connaît le concept. Domain au centre, Data en périphérie, Presentation qui ne parle qu’au Domain. Le problème en Flutter, c’est que tout ça vit dans le même package lib/. Rien n’empêche un import interdit, c’est juste une convention.

Ma solution : séparer chaque couche dans son propre package Dart.

packages/
├── core/ # Utils partagés, thème, i18n
├── domain/ # Entités, UseCases, interfaces Repository
├── data/ # DTOs, DataSources, implémentations Repo
├── presentation/ # Screens, Widgets, Connectors, Providers
└── lint_rules/ # Règles lint custom (custom_lint)

Le truc magique, c’est que le compilateur Dart enforce les dépendances entre packages. Si tu essaies d’importer data depuis presentation, c’est une erreur de compilation. Pas un warning. Pas un lint. Une erreur rouge qui bloque le build.

PackagePeut importerNE PEUT PAS importer
corerien d’internedomain, data, presentation
domaincoredata, presentation
datacore, domainpresentation
presentationcore, domaindata
app (root)tout,

Quand un agent IA écrit import 'package:myapp_data/auth/datasources/...' dans un fichier de presentation/, le build plante. L’agent reçoit l’erreur, comprend la contrainte, et corrige. Sans intervention humaine.

Les custom lint rules : le deuxième verrou

Le compilateur empêche les imports interdits. Mais il y a des patterns qu’il ne peut pas attraper seul. Pour ça, j’ai créé un package de lint rules custom avec custom_lint.

Six règles, chacune avec une raison précise :

RègleCe qu’elle empêche
molecule_no_riverpodLes molecules ne peuvent pas importer Riverpod
organism_no_riverpodPareil pour les organisms
connector_must_be_consumerLes connectors doivent être des ConsumerWidget
max_function_linesMax 50 lignes par fonction
max_class_linesMax 200 lignes par classe
one_public_class_per_fileUne seule classe publique par fichier

La plus importante : molecule_no_riverpod. Elle garantit que les composants UI de base (les “molecules”) reçoivent toutes leurs données via le constructeur. Zéro dépendance à l’état global. Zéro mock nécessaire pour les tester.

Quand l’IA génère un widget molecule et essaie d’injecter un ref.watch() dedans, le lint la bloque. Elle doit passer les données en paramètres. Ça force une architecture propre mécaniquement.

Atomic Design : chaque widget a un rôle

J’utilise une variante d’Atomic Design adaptée à Flutter + Riverpod. Quatre types de widgets, avec des règles claires :

Molecule, Utilise uniquement les widgets primitifs Flutter (Text, Container, Icon). Pas de Riverpod, pas de widgets custom. Lint-enforced.

Organism, Combine des molecules. Toujours pur : toutes les données passent par le constructeur.

Connector, Le pont entre Riverpod et les widgets purs. C’est un ConsumerWidget qui watch() des providers et passe les données aux organisms. Gère les états loading/error/data.

Screen, Page complète. Gère la navigation, l’analytics, l’orchestration.

L’arbre de décision est simple :

Besoin de Riverpod ? → Oui → Page complète ? → Oui → SCREEN / Non → CONNECTOR → Non → Utilise des widgets custom ? → Oui → ORGANISM / Non → MOLECULE

Le bénéfice direct pour le testing : les molecules et organisms se testent sans aucun mock. Juste un pumpWidget(MaterialApp(home: ScoreBadge(score: 150))) et c’est parti.

Le DI wiring : le seul endroit qui voit tout

Le pattern de Dependency Injection utilise des provider stubs dans le domain :

// packages/domain/, déclare le type
@Riverpod(keepAlive: true)
AuthRepository authRepository(Ref ref) {
 throw UnimplementedError('Must be overridden in app');
}

L’implémentation concrète est câblée uniquement dans lib/di/, le seul endroit du projet qui a le droit d’importer data ET presentation :

// lib/main.dart, câble le concret
ProviderScope(
 overrides: [
 authRepositoryProvider.overrideWith((ref) {
 return AuthRepositoryImpl(
 remoteDataSource: AuthRemoteDataSource(),
 );
 }),
 ],
 child: const MainApp(),
);

Presentation ne sait jamais d’où viennent les données. Elle demande un AuthRepository, elle le reçoit. Que ça vienne de Firestore, d’une API REST, ou d’un mock en test, le code UI ne change pas.

Pourquoi c’est un game-changer avec l’IA

Voilà ce que cette architecture change concrètement quand je travaille avec des agents IA :

Avant (architecture classique) :

  • L’agent prend des raccourcis → le code compile → je review tout manuellement → je corrige 60% de ce qu’il produit
  • La dette technique s’accumule entre les sessions
  • Chaque nouvel agent/contexte “oublie” les conventions

Après (Layou Dev Kit) :

  • L’agent prend un raccourci → le build plante → il se corrige tout seul
  • L’architecture est structurellement incassable
  • Le flutter analyze avec les custom lint rules attrape le reste
Sans Layou Dev KitAvec Layou Dev Kit
Import interditÇa compile (mais c’est cassé)Erreur de compilation ❌
Widget trop grosPersonne ne le voitLint error à 200 lignes
Molecule avec RiverpodÇa marcheLint error
Test d’un widgetMock hellZéro mock (molecules/organisms)
Nouvel agent IADoit relire toutes les conventionsLe compilateur le guide

Le vrai insight : je n’ai pas besoin de faire confiance à l’IA. L’architecture elle-même est le garde-fou. L’IA peut halluciner autant qu’elle veut, si ça compile et que flutter analyze passe, le code respecte l’architecture.

Ce que ça coûte

Je serais malhonnête si je disais que c’était gratuit. Le setup initial prend du temps :

  • Melos + packages séparés : 2-3 jours de scaffolding au premier projet
  • Custom lint rules : 1 jour d’écriture (avec des tests)
  • build_runner : plus lent sur un monorepo (j’utilise des build.yaml ciblés pour optimiser)
  • Courbe d’apprentissage : un dev qui rejoint le projet doit comprendre les 4 couches

Mais après ? Chaque nouveau feature suit le même pattern mécaniquement. L’IA scaffold un nouveau module en respectant la structure. Les tests s’écrivent presque tout seuls. Et surtout : je n’ai jamais besoin de refactorer l’architecture, elle tient depuis 3 apps et 100k+ lignes de code.

Pour qui c’est fait

Cette architecture n’est pas pour tout le monde. Elle fait sens si :

  • Tu maintiens une app Flutter sur le long terme (pas un prototype)
  • Tu travailles avec des agents IA qui codent pour toi
  • Tu es solo ou en petite équipe et tu ne peux pas te permettre de review chaque ligne
  • Tu veux du code testable sans douleur

Si tu fais un hackathon ou un MVP jetable, c’est overkill. Mais si tu construis un produit qui doit tourner des années ? C’est de l’infrastructure. Et comme toute bonne infrastructure, une fois en place, tu te demandes comment tu faisais sans.

Ressources

Tu veux structurer ton prochain projet Flutter pour que l’IA code proprement ? Commence par séparer tes couches en packages. Le compilateur fera le reste.

#Flutter #Clean Architecture #Riverpod #IA #agents IA #architecture logicielle #Dart #Melos #custom lint #monorepo #indie dev #qualité code