Claude Code pour l'itération UI Flutter : une boucle pratique pour transformer des user stories en arbres de widgets, états et navigation tout en gardant les changements modulaires et faciles à relire.

Le travail UI rapide en Flutter commence souvent bien. Vous ajustez un layout, ajoutez un bouton, déplacez un champ, et l'écran s'améliore vite. Le problème survient après quelques passes, quand la vitesse se transforme en un tas de changements que personne ne veut relire.
Les équipes rencontrent généralement les mêmes échecs :
Une grande cause est l'approche « une grosse requête » : décrire la fonctionnalité entière, demander l'ensemble complet d'écrans et accepter une sortie massive. L'assistant tente d'aider, mais touche trop de parties du code à la fois. Cela rend les changements brouillons, difficiles à relire et risqués à fusionner.
Une boucle répétable corrige cela en imposant de la clarté et en limitant le rayon d'impact. Au lieu de « construire la fonctionnalité, » faites ceci de manière répétée : choisissez une user story, générez la plus petite tranche d'UI qui la prouve, ajoutez uniquement l'état nécessaire pour cette tranche, puis câblez la navigation pour une voie. Chaque passe reste assez petite pour être relue, et les erreurs sont faciles à annuler.
L'objectif ici est un workflow pratique pour transformer des user stories en écrans concrets, gestion d'état et flux de navigation sans perdre le contrôle. Bien fait, vous obtenez des pièces UI modulaires, des diffs plus petits et moins de surprises quand les exigences changent.
Les user stories sont écrites pour des humains, pas pour des arbres de widgets. Avant de générer quoi que ce soit, convertissez la story en une petite spec UI qui décrit le comportement visible. « Terminé » doit être testable : ce que l'utilisateur peut voir, toucher et confirmer, pas si le design « semble moderne ».
Une façon simple de garder le scope concret est de diviser la story en quatre catégories :
Si la story reste floue, répondez à ces questions en langage simple :
Ajoutez des contraintes tôt parce qu'elles guident chaque choix de layout : bases du thème (couleurs, espacements, typographie), responsivité (d'abord téléphone en portrait, puis tablettes), et minima d'accessibilité comme la taille des cibles tactiles, l'échelle du texte lisible et des labels significatifs pour les icônes.
Enfin, décidez ce qui est stable vs flexible pour éviter de churner la base de code. Les éléments stables sont ceux dont d'autres fonctionnalités dépendent, comme les noms de routes, les modèles de données et les APIs existantes. Les éléments flexibles sont sûrs à itérer, comme la structure du layout, la microcopie et la composition exacte des widgets.
Exemple : « En tant qu'utilisateur, je peux enregistrer un élément dans Favoris depuis l'écran de détail. » Une spec UI réalisable pourrait être :
C'est suffisant pour construire, relire et itérer sans deviner.
Des diffs petits ne signifient pas travailler plus lentement. Ils rendent chaque changement facile à relire, facile à annuler et difficile à casser. La règle la plus simple : un écran ou une interaction par itération.
Choisissez une tranche précise avant de commencer. « Ajouter un état vide à l'écran Commandes » est une bonne tranche. « Refondre tout le flow Commandes » ne l'est pas. Visez un diff qu'un coéquipier peut comprendre en une minute.
Une structure de dossiers stable aide aussi à contenir les changements. Une organisation simple, feature-first, évite de disperser widgets et routes à travers l'app :
lib/
features/
orders/
screens/
widgets/
state/
routes.dart
Gardez les widgets petits et composés. Quand un widget a des entrées et sorties claires, vous pouvez changer le layout sans toucher la logique d'état, et modifier l'état sans réécrire l'UI. Privilégiez les widgets qui prennent des valeurs simples et des callbacks, pas de l'état global.
Une boucle qui reste relisible :
Fixez une règle stricte : chaque changement doit être facile à annuler ou isoler. Évitez les refactors non liés pendant que vous itérez sur un écran. Si vous remarquez des problèmes non liés, notez-les et corrigez-les dans un commit séparé.
Si votre outil supporte snapshots et rollback, utilisez chaque tranche comme point de snapshot. Certaines plateformes vibe-coding comme Koder.ai incluent des snapshots et rollback, ce qui peut rendre l'expérimentation plus sûre quand vous tentez un changement UI audacieux.
Une habitude qui garde les premières itérations calmes : privilégier l'ajout de nouveaux widgets plutôt que l'édition de widgets partagés. Les composants partagés sont là où de petits changements deviennent de gros diffs.
Le travail UI rapide reste sûr quand vous séparez la réflexion du tapotement. Commencez par obtenir un plan d'arbre de widgets clair avant de générer du code.
Demandez uniquement un aperçu d'arbre de widgets. Vous voulez des noms de widgets, la hiérarchie et ce que chaque partie affiche. Pas encore de code. C'est là que vous repérez des états manquants, des écrans vides et des choix de layout étranges, tant que tout est encore peu coûteux à changer.
Demandez une répartition des composants avec leurs responsabilités. Gardez chaque widget ciblé : un widget rend l'en-tête, un autre rend la liste, un autre gère l'UI vide/erreur. Si quelque chose nécessitera de l'état plus tard, notez-le maintenant mais n'implémentez pas encore.
Générez le scaffold de l'écran et des widgets stateless. Commencez par un fichier d'écran unique avec du contenu placeholder et des TODO clairs. Gardez les inputs explicites (params du constructeur) pour pouvoir injecter l'état réel plus tard sans réécrire l'arbre.
Faites une passe séparée pour le style et les détails de layout : espacements, typographie, theming et comportement responsive. Traitez le style comme un diff à part pour que les revues restent simples.
Exposez les contraintes dès le départ pour que l'assistant n'invente pas d'UI que vous ne pouvez pas livrer :
Exemple concret : la user story est « En tant qu'utilisateur, je peux consulter mes éléments sauvegardés et en supprimer un. » Demandez un arbre de widgets incluant une app bar, une liste avec des lignes d'item et un état vide. Puis demandez une répartition comme SavedItemsScreen, SavedItemTile, EmptySavedItems. Ce n'est qu'après que vous générez le scaffold stateless avec des données factices, puis que vous ajoutez le styling (séparateurs, padding et un bouton de suppression clair) dans une passe séparée.
L'itération UI se casse quand chaque widget commence à prendre des décisions. Gardez l'arbre de widgets idiot : il doit lire l'état et rendre, pas contenir des règles métiers.
Commencez par nommer les états en langage simple. La plupart des features ont besoin de plus que « loading » et « done » :
Puis listez les événements qui peuvent changer l'état : taps, soumission de formulaire, pull-to-refresh, retour, retry et « l'utilisateur a édité un champ ». Faire cela en amont évite les suppositions plus tard.
Choisissez une approche d'état pour la feature et tenez-vous-y. L'objectif n'est pas « le meilleur pattern », mais des diffs cohérents.
Pour un écran petit, un contrôleur simple (ChangeNotifier ou ValueNotifier) suffit souvent. Placez la logique en un seul endroit :
Avant d'ajouter du code, écrivez les transitions d'état en anglais simple. Exemple pour un écran de login :
"Quand l'utilisateur appuie sur Se connecter : définir Loading. Si l'email est invalide : rester en Partial input et afficher un message inline. Si le mot de passe est incorrect : définir Error avec un message et activer Retry. Si succès : définir Success et naviguer vers Home."
Puis générez le code Dart minimal qui correspond à ces phrases. Les revues restent simples car vous pouvez comparer le diff aux règles.
Rendez la validation explicite. Décidez ce qui se passe quand les champs sont invalides :
Quand ces réponses sont écrites, votre UI reste propre et le code d'état reste petit.
Une bonne navigation commence par une petite carte, pas un tas de routes. Pour chaque user story, notez quatre moments : où l'utilisateur entre, l'étape suivante la plus probable, comment il annule, et ce que signifie « retour » (retour à l'écran précédent ou retour à un état sûr).
Une simple carte de routes doit répondre aux questions qui causent généralement du retiming :
Puis définissez les paramètres passés entre écrans. Soyez explicite : IDs (productId, orderId), filtres (plage de dates, statut) et données brouillon (formulaire partiellement rempli). Si vous sautez cette étape, vous allez finir par enfouir l'état dans des singletons globaux ou reconstruire des écrans pour "retrouver" le contexte.
Les deep links comptent même si vous ne les livrez pas le jour 1. Décidez ce qui arrive quand un utilisateur arrive au milieu d'un flux : pouvez-vous charger les données manquantes, ou devez-vous rediriger vers un écran d'entrée sûr ?
Décidez aussi quels écrans doivent renvoyer des résultats. Exemple : un écran "Select Address" renvoie un addressId, et l'écran de checkout se met à jour sans refresh complet. Gardez la forme du retour petite et typée pour que les changements restent faciles à relire.
Avant de coder, signalez les cas limites : changements non sauvegardés (afficher un dialogue de confirmation), authentification requise (mettre en pause et reprendre après connexion), et données manquantes ou supprimées (afficher une erreur et une issue de sortie claire).
Quand vous itérez vite, le vrai risque n'est pas l'UI « incorrecte ». C'est une UI non relisable. Si un coéquipier ne peut pas dire ce qui a changé, pourquoi ça a changé et ce qui est resté stable, chaque itération suivante ralentit.
Une règle utile : verrouillez d'abord les interfaces, puis laissez les internes bouger. Stabilisez les props publiques des widgets (inputs), petits modèles UI et arguments de route. Une fois nommés et typés, vous pouvez remodeler l'arbre de widgets sans casser le reste de l'app.
Demandez un plan favorable aux diffs avant de générer du code. Vous voulez un plan qui dit quels fichiers changeront et lesquels resteront intacts. Cela concentre les revues et évite les refactors accidentels qui modifient le comportement.
Patterns qui maintiennent de petits diffs :
Supposons la story : « En tant qu'acheteur, je peux modifier mon adresse de livraison depuis le checkout. » Verrouillez d'abord les args de route : CheckoutArgs(cartId, shippingAddressId) reste stable. Puis itérez à l'intérieur de l'écran. Quand le layout est stable, découpez en AddressForm, AddressSummary et SaveBar.
Si la gestion d'état change (par exemple, la validation passe du widget à un CheckoutController), la revue reste lisible : les fichiers UI changent principalement pour le rendu, tandis que le controller montre la logique centralisée en un seul endroit.
La façon la plus rapide de ralentir est de demander à l'assistant de tout changer en même temps. Si un commit touche layout, état et navigation, les reviewers ne sauront pas ce qui a cassé, et le rollback devient compliqué.
Une habitude plus sûre : une intention par itération : façonnez l'arbre de widgets, puis câblez l'état, puis connectez la navigation.
Un problème courant est de laisser le code généré inventer un nouveau pattern à chaque écran. Si une page utilise Provider, la suivante setState et la troisième un controller personnalisé, l'app devient rapidement incohérente. Choisissez un petit ensemble de patterns et faites-les respecter.
Autre erreur : mettre du travail asynchrone directement dans build(). Ça peut sembler correct pour une démo rapide, mais ça déclenche des appels répétés aux rebuilds, des clignotements et des bugs difficiles à tracer. Déplacez l'appel dans initState(), un view model ou un controller dédié, et gardez build() concentré sur le rendu.
Le nommage est un piège discret. Du code qui compile mais qui s'appelle Widget1, data2 ou temp rend les refactors futurs pénibles. Des noms clairs aident aussi l'assistant à produire des modifications de suivi meilleures car l'intention est évidente.
Garde-fous pour éviter le pire :
build()Une correction visuelle classique est d'ajouter un Container, Padding, Align puis SizedBox jusqu'à ce que ça ressemble. Après quelques passes, l'arbre devient illisible.
Si un bouton est mal aligné, essayez d'abord d'enlever des wrappers, d'utiliser un seul widget parent de layout ou d'extraire un petit widget avec ses propres contraintes.
Exemple : un écran de checkout où le total change de taille lors du chargement. Un assistant pourrait entourer la ligne du prix de plus en plus de widgets pour la « stabiliser ». Une solution plus propre est de réserver l'espace avec un placeholder de chargement simple tout en gardant la structure de la ligne inchangée.
Avant de committer, faites une passe de deux minutes qui vérifie la valeur utilisateur et vous protège des régressions surprises. L'objectif n'est pas la perfection. C'est faire en sorte que cette itération soit facile à relire, tester et annuler.
Relisez la user story une fois, puis vérifiez ces éléments contre l'app en cours d'exécution (ou au moins contre un test de widget simple) :
Un petit check réaliste : si vous avez ajouté un nouvel écran Détails de commande, vous devriez pouvoir (1) l'ouvrir depuis la liste, (2) voir un spinner de chargement, (3) simuler une erreur, (4) voir une commande vide, et (5) appuyer sur back pour revenir à la liste sans sauts bizarres.
Si votre workflow supporte snapshots et rollback, prenez un snapshot avant des changements UI plus importants. Certaines plateformes comme Koder.ai proposent cela, et ça aide à itérer plus vite sans risquer la branche principale.
User story : « En tant qu'acheteur, je peux parcourir des items, ouvrir une page de détails, sauvegarder un item en favoris puis voir mes favoris plus tard. » L'objectif est de passer des mots aux écrans en trois petites étapes relisables.
Itération 1 : concentrez-vous uniquement sur l'écran de liste. Créez un arbre de widgets suffisant pour rendre mais pas lié à de vraies données : un Scaffold avec un AppBar, une ListView de lignes factices et une UI claire pour loading et empty. Gardez l'état simple : loading (affiche un CircularProgressIndicator), empty (message court et éventuellement un bouton Réessayer) et ready (affiche la liste).
Itération 2 : ajoutez l'écran de détails et la navigation. Restez explicite : onTap pousse une route et passe un petit objet paramètre (par exemple : id de l'item, titre). Démarrez la page de détails en lecture seule avec un titre, une description factice et un bouton Favori. Le but est de respecter la story : liste -> détails -> retour, sans flux supplémentaires.
Itération 3 : introduisez la mise à jour de l'état des favoris et du feedback UI. Ajoutez une source de vérité unique pour les favoris (même en mémoire), et reliez-la aux deux écrans. Taper Favori met à jour l'icône immédiatement et affiche une confirmation (ex. SnackBar). Puis ajoutez un écran Favoris qui lit le même état et gère l'état vide.
Un diff relisible ressemble typiquement à :
browse_list_screen.dart : arbre de widgets + UI loading/empty/readyitem_details_screen.dart : layout UI et accepte des params de navigationfavorites_store.dart : holder d'état minimal et méthodes de mise à jourapp_routes.dart : routes et helpers de navigation typésfavorites_screen.dart : lit l'état et affiche empty/list UISi un fichier devient « l'endroit où tout arrive », scindez-le avant d'aller plus loin. Des petits fichiers aux noms clairs accélèrent la prochaine itération.
Si le workflow ne fonctionne que quand vous êtes « dans la zone », il cassera dès que vous changerez d'écran ou qu'un coéquipier touchera la feature. Faites de la boucle une habitude en l'écrivant et en posant des garde-fous sur la taille des changements.
Utilisez un template d'équipe pour que chaque itération commence avec les mêmes entrées et produise le même type de sortie. Gardez-le court mais spécifique :
Cela réduit les chances que l'assistant invente de nouveaux patterns en cours de feature.
Choisissez une définition de petit facile à faire respecter en revue de code. Par exemple, limitez chaque itération à un nombre de fichiers et séparez refactors UI et changements de comportement.
Un ensemble simple de règles :
Ajoutez des points de contrôle pour pouvoir annuler rapidement une mauvaise étape. Au minimum, taggez les commits ou gardez des checkpoints locaux avant des refactors majeurs. Si votre workflow supporte snapshots et rollback, utilisez-les agressivement.
Si vous voulez un workflow basé sur le chat qui peut générer et affiner des apps Flutter de bout en bout, Koder.ai inclut un mode de planification qui vous aide à revoir un plan et les fichiers attendus avant de les appliquer.
Utilisez d'abord un mini spec UI testable. Rédigez 3–6 lignes qui couvrent :
Puis ne construisez que cette tranche (souvent un écran + 1–2 widgets).
Convertissez la story en quatre catégories :
Si vous ne pouvez pas décrire rapidement la vérification d'acceptation, la story est encore trop floue pour produire un diff propre.
Commencez par générer uniquement un plan d'arbre de widgets (noms + hiérarchie + ce que chaque partie affiche). Pas de code.
Ensuite demandez une répartition des responsabilités des composants (ce que chaque widget gère).
Ce n'est qu'après que vous générez le scaffold stateless avec des inputs explicites (valeurs + callbacks) et que vous faites le styling dans une passe séparée.
Traitez-le comme une règle stricte : une intention par itération.
Si un seul commit modifie layout, état et routes ensemble, les reviewers ne sauront pas ce qui a causé un bug et le rollback devient compliqué.
Gardez les widgets « dumb » : ils doivent rendre l'état, pas contenir les règles métiers.
Un défaut pratique par défaut :
Évitez les appels asynchrones dans —cela provoque des appels répétés au re-render.
Définissez d'abord les états et transitions en langage clair avant de coder.
Pattern exemple :
Ensuite listez les événements qui les font évoluer (refresh, retry, submit, edit). Le code devient plus facile à comparer avec les règles écrites.
Écrivez une petite « carte de flux » pour la story :
Privilégiez les dossiers orientés feature pour contenir les changements. Par exemple :
lib/features/<feature>/screens/lib/features/<feature>/widgets/lib/features/<feature>/state/lib/features/<feature>/routes.dartEnsuite concentrez chaque itération sur un dossier feature et évitez les refactors non liés.
Règle simple : stabilisez les interfaces, pas les internes.
Les reviewers veulent surtout que inputs/outputs restent stables même si la mise en page bouge.
Faites une vérification rapide de deux minutes :
Si votre workflow le permet (snapshots/rollback), prenez un instantané avant un refactor de layout plus important pour pouvoir revenir en arrière facilement.
build()Verrouillez aussi ce qui voyage entre écrans (IDs, filtres, données brouillon) pour ne pas finir par cacher du contexte dans des singletons.