Mon architecture Flutter est 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
DataSourceFirestore dans un widget - Il crée un
StatefulWidgetde 300 lignes qui mélange UI, état, et appels réseau - Il met trois classes dans le même fichier
- Il utilise
setStateau 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.
| Package | Peut importer | NE PEUT PAS importer |
|---|---|---|
core | rien d’interne | domain, data, presentation |
domain | core | data, presentation |
data | core, domain | presentation |
presentation | core, domain | data |
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ègle | Ce qu’elle empêche |
|---|---|
molecule_no_riverpod | Les molecules ne peuvent pas importer Riverpod |
organism_no_riverpod | Pareil pour les organisms |
connector_must_be_consumer | Les connectors doivent être des ConsumerWidget |
max_function_lines | Max 50 lignes par fonction |
max_class_lines | Max 200 lignes par classe |
one_public_class_per_file | Une 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 analyzeavec les custom lint rules attrape le reste
| Sans Layou Dev Kit | Avec Layou Dev Kit | |
|---|---|---|
| Import interdit | Ça compile (mais c’est cassé) | Erreur de compilation ❌ |
| Widget trop gros | Personne ne le voit | Lint error à 200 lignes |
| Molecule avec Riverpod | Ça marche | Lint error |
| Test d’un widget | Mock hell | Zéro mock (molecules/organisms) |
| Nouvel agent IA | Doit relire toutes les conventions | Le 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.yamlciblé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
- 📹 Ma vidéo sur la Clean Architecture Flutter, walkthrough complet
- 🔧 BeeDone sur le web, construit avec cette architecture
- 🎨 Muse Otter, construit avec cette architecture
- 📦 Melos, l’outil de monorepo Dart
- 📦 custom_lint, pour écrire vos propres règles
- 📦 Riverpod, le state management
- 📦 Freezed, génération de code pour les entities
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.