Claude Code für Flutter-UI-Iteration: eine praktische Schleife, um User Stories in Widget-Bäume, State und Navigation zu verwandeln, dabei modular zu bleiben und Änderungen prüfbar zu halten.

Schnelle Flutter-UI-Arbeit beginnt oft gut. Du passt ein Layout an, fügst einen Button hinzu, verschiebst ein Feld — und der Screen wird schnell besser. Das Problem zeigt sich nach ein paar Durchläufen, wenn Tempo in einen Berg von Änderungen umschlägt, den niemand gerne reviewt.
Teams stolpern üblicherweise über die gleichen Fallen:
Eine große Ursache ist der „one big prompt“-Ansatz: die ganze Funktion beschreiben, um das vollständige Set an Screens bitten und eine große Ausgabe akzeptieren. Der Assistent versucht zu helfen, aber berührt zu viele Teile des Codes auf einmal. Das macht Änderungen unordentlich, schwer zu reviewen und riskant zum Mergen.
Eine wiederholbare Schleife löst das, indem sie Klarheit erzwingt und die Blast-Radius begrenzt. Statt „baue das Feature", mach wiederholt Folgendes: wähle eine User Story, generiere die kleinste UI-Scheibe, die sie beweist, füge nur den für diese Scheibe nötigen State hinzu und verdrahte Navigation für einen Pfad. Jede Iteration bleibt klein genug zum Review, und Fehler sind leicht rückgängig zu machen.
Das Ziel ist ein praktischer Workflow, um User Stories in konkrete Screens, State-Handling und Navigationsflüsse zu verwandeln, ohne die Kontrolle zu verlieren. Gut gemacht endest du mit modularen UI-Stücken, kleineren Diffs und weniger Überraschungen, wenn sich Anforderungen ändern.
User Stories sind für Menschen geschrieben, nicht für Widget-Bäume. Bevor du etwas generierst, verwandle die Story in eine kleine UI-Spezifikation, die sichtbares Verhalten beschreibt. „Fertig" sollte prüfbar sein: was der Nutzer sehen, antippen und bestätigen kann — nicht, ob das Design „modern wirkt".
Eine einfache Methode, den Umfang konkret zu halten, ist die Aufteilung der Story in vier Bereiche:
Wenn die Story noch unklar ist, beantworte diese Fragen in einfacher Sprache:
Füge früh Constraints hinzu, denn sie leiten jede Layout-Entscheidung: Theme-Grundlagen (Farben, Abstände, Typografie), Responsivität (zuerst Phone-Portrait, dann Tablet-Breiten) und Mindestanforderungen an Accessibility wie Tippflächen, lesbare Textskalierung und aussagekräftige Labels für Icons.
Entscheide schließlich, was stabil vs. flexibel ist, damit du den Code nicht unnötig churnst. Stabil sind Dinge, von denen andere Features abhängen, z. B. Routennamen, Datenmodelle und vorhandene APIs. Flexibel sind sichere Iterationspunkte wie Layout-Struktur, Microcopy und die genaue Widget-Komposition.
Beispiel: „Als Nutzer kann ich ein Item auf der Detailseite zu Favoriten speichern." Eine buildbare UI-Spezifikation könnte sein:
Das reicht, um zu bauen, zu reviewen und iterativ zu verbessern, ohne zu spekulieren.
Kleine Diffs bedeuten nicht langsameres Arbeiten. Sie machen jede UI-Änderung leicht zu reviewen, einfach rückgängig zu machen und schwer zu brechen. Die einfachste Regel: eine Seite oder eine Interaktion pro Iteration.
Wähle vor dem Start einen engen Slice. „Füge einen Empty-State zum Orders-Screen hinzu" ist ein guter Slice. „Überarbeite den gesamten Orders-Flow" ist es nicht. Ziel ist ein Diff, das ein Teammitglied in einer Minute verstehen kann.
Eine stabile Ordnerstruktur hilft außerdem, Änderungen eingegrenzt zu halten. Ein einfaches, feature-first Layout verhindert, dass Widgets und Routen überall verstreut werden:
lib/
features/
orders/
screens/
widgets/
state/
routes.dart
Halte Widgets klein und komponierbar. Wenn ein Widget klare Inputs und Outputs hat, kannst du Layout ändern, ohne State-Logik anzufassen, und State ändern, ohne UI neu zu schreiben. Bevorzuge Widgets, die einfache Werte und Callbacks akzeptieren, statt globalen State zu lesen.
Eine reviewbare Schleife:
Setze eine harte Regel: jede Änderung muss leicht revertierbar oder isolierbar sein. Vermeide „Drive-by“-Refactors während du an einem Screen iterierst. Wenn dir nebenbei Probleme auffallen, notiere sie und behandle sie in einem separaten Commit.
Wenn dein Tool Snapshots und Rollback unterstützt, nutze jeden Slice als Snapshot-Punkt. Einige Vibe-Coding-Plattformen wie Koder.ai bieten Snapshots und Rollback, was Experimente sicherer macht, wenn du mutige UI-Änderungen ausprobierst.
Ein weiteres Verhalten, das frühe Iterationen ruhig hält: füge lieber neue Widgets hinzu, statt geteilte zu bearbeiten. Geteilte Komponenten sind der Ort, an dem kleine Änderungen große Diffs auslösen.
Schnelle UI-Arbeit bleibt sicher, wenn du Denken und Tippen trennst. Beginne mit einem klaren Widget-Tree-Plan, bevor du Code generierst.
Bitte zuerst nur um eine Widget-Tree-Outline. Du willst Widget-Namen, Hierarchie und was jeder Teil zeigt. Kein Code. Hier bemerkst du fehlende States, leere Screens und merkwürdige Layout-Entscheidungen, solange alles noch billig änderbar ist.
Bitte um eine Komponenten-Aufschlüsselung mit Verantwortlichkeiten. Halte jedes Widget fokussiert: eins rendert den Header, ein anderes die Liste, ein drittes die Empty/Error-UI. Wenn später State nötig wird, notiere es, implementiere ihn aber noch nicht.
Generiere das Screen-Scaffold und stateless Widgets separat. Beginne mit einer einzigen Screen-Datei mit Platzhaltern und klaren TODOs. Halte Inputs explizit (Konstruktorparameter), damit du später echten State anschließen kannst, ohne den Baum umzuschreiben.
Mach einen separaten Durchgang für Styling und Layout-Details: Abstände, Typografie, Theming und responsives Verhalten. Behandle Styling als eigenen Diff, damit Reviews einfach bleiben.
Setze Constraints voran, damit der Assistent keine nicht-versendbare UI erfindet:
Konkretes Beispiel: Die User Story ist „As a user, I can review my saved items and remove one." Bitte um einen Widget-Tree, der eine AppBar, eine Liste mit Item-Rows und einen Empty-State enthält. Fordere dann eine Aufschlüsselung wie SavedItemsScreen, SavedItemTile, EmptySavedItems. Erst danach das Scaffold mit stateless Widgets und Fake-Daten generieren, und zuletzt Styling (Divider, Padding, klarer Remove-Button) in einem separaten Pass hinzufügen.
UI-Iteration scheitert, wenn jedes Widget Entscheidungen treffen will. Halte den Widget-Baum dumm: er soll State lesen und rendern, nicht Geschäftslogik enthalten.
Beginne damit, die States in Worten zu benennen. Die meisten Features brauchen mehr als nur „loading" und „done":
Liste dann Events auf, die den State ändern: taps, Form-Submit, Pull-to-Refresh, Back, Retry und „Nutzer hat ein Feld verändert". Das verhindert spätere Ratespiele.
Wähle einen State-Ansatz für das Feature und bleibe dabei. Es geht nicht um das „beste“ Pattern, sondern um konsistente Diffs.
Für einen kleinen Screen reicht oft ein einfacher Controller (z. B. ChangeNotifier oder ValueNotifier). Lege die Logik an einer Stelle ab:
Bevor du Code schreibst, beschreibe die State-Transitionen in einfachem Englisch. Beispiel für einen Login-Screen:
"Wenn der Nutzer Sign in tappt: setze Loading. Wenn die E-Mail ungültig ist: bleibe in Partial input und zeige eine Inline-Meldung. Wenn das Passwort falsch ist: setze Error mit einer Nachricht und aktiviere Retry. Bei Erfolg: setze Success und navigiere zu Home."
Erzeuge dann minimalen Dart-Code, der diesen Sätzen entspricht. Reviews bleiben einfach, weil du den Diff mit den Regeln vergleichen kannst.
Mache Validierung explizit. Entscheide, was passiert, wenn Eingaben ungültig sind:
Gute Navigation beginnt als kleine Karte, nicht als Berg von Routen. Für jede User Story notiere vier Momente: wo der Nutzer einsteigt, der wahrscheinlichste nächste Schritt, wie er abbrechen kann und was "Back" bedeutet (zurück zur vorherigen Seite oder zu einem sicheren Home-State).
Eine einfache Routen-Map sollte die Fragen beantworten, die sonst für Rework sorgen:
Definiere dann die Parameter, die zwischen Bildschirmen übergeben werden. Sei explizit: IDs (productId, orderId), Filter (Datumsbereich, Status) und Draft-Daten (teilweise ausgefüllte Formulare). Wenn du das auslässt, endest du damit, Kontext in globalen Singletons zu verstecken oder Screens so umzubauen, dass sie Kontext „finden".
Deep Links sind wichtig, auch wenn du sie nicht am ersten Tag auslieferst. Entscheide, was passiert, wenn ein Nutzer mitten im Flow landet: kannst du fehlende Daten nachladen oder leitest du auf einen sicheren Einstieg um?
Entscheide außerdem, welche Screens Ergebnisse zurückgeben sollten. Beispiel: ein Select Address-Screen gibt eine addressId zurück, und der Checkout aktualisiert sich ohne kompletten Refresh. Halte die Rückgabeform klein und typisiert, damit Änderungen leicht reviewbar bleiben.
Rufe Edge-Cases vor dem Kodieren auf: ungespeicherte Änderungen (Confirm-Dialog), Auth nötig (Pause und Fortsetzen nach Login) und fehlende oder gelöschte Daten (Fehler anzeigen und klaren Ausweg bieten).
Beim schnellen Iterieren ist das eigentliche Risiko nicht „falsche UI", sondern unüberprüfbare UI. Wenn ein Kollege nicht erkennen kann, was sich geändert hat, warum und was stabil blieb, wird jede weitere Iteration langsamer.
Eine Regel, die hilft: sperre zuerst die Interfaces, dann dürfen die Interna sich bewegen. Stabilisiere öffentliche Widget-Props (Inputs), kleine UI-Modelle und Routengegenstände. Sobald diese benannt und typisiert sind, kannst du den Widget-Baum umbauen, ohne den Rest der App zu brechen.
Fordere vor dem Generieren einen diff-freundlichen Plan an. Du willst einen Plan, der sagt, welche Dateien sich ändern und welche unangetastet bleiben. Das macht Reviews fokussiert und verhindert unbeabsichtigte Refactors, die Verhalten ändern.
Muster, die Diffs klein halten:
Angenommen, die Story ist „Als Käufer kann ich meine Lieferadresse im Checkout bearbeiten." Sperre zuerst die Route-Args: CheckoutArgs(cartId, shippingAddressId) bleibt stabil. Iteriere dann innerhalb des Screens. Sobald das Layout steht, splitte in AddressForm, AddressSummary und SaveBar.
Wenn sich das State-Handling ändert (z. B. Validierung wandert vom Widget in einen CheckoutController), bleibt das Review lesbar: UI-Dateien zeigen meist nur Rendering-Änderungen, während der Controller die Logikänderung an einer Stelle zeigt.
Der schnellste Weg, langsamer zu werden, ist, den Assistenten alles auf einmal ändern zu lassen. Wenn ein Commit Layout, State und Navigation trifft, können Reviewer nicht erkennen, was gebrochen hat. Ein sicherer Habit ist: eine Intention pro Iteration—erst den Widget-Tree formen, dann State verdrahten, dann Navigation verbinden.
Ein häufiges Problem ist, dass generierter Code auf jeder Seite ein neues Pattern erfindet. Seite A nutzt Provider, Seite B setState, Seite C einen Custom-Controller — die App wird schnell inkonsistent. Wähle ein kleines Set an Patterns und halte dich daran.
Ein anderer Fehler ist, asynchrone Arbeit direkt in build() zu legen. Das mag in einer schnellen Demo funktionieren, führt aber zu wiederholten Calls bei Rebuilds, Flicker und schwer nachverfolgbaren Bugs. Verschiebe den Aufruf in initState(), ein ViewModel oder einen dedizierten Controller und halte build() auf Rendering fokussiert.
Benennungen sind eine stille Falle. Code, der kompiliert, aber Namen wie Widget1, data2 oder temp verwendet, macht spätere Refactors zur Qual. Klare Namen helfen dem Assistenten außerdem, bessere Folgeänderungen zu erzeugen, weil die Absicht offensichtlich ist.
Schutzvorrichtungen, die das Schlimmste verhindern:
build()\n- Benenne Platzhalter um, bevor du weitere Funktionalität hinzufügst\n- Extrahiere Widgets, statt die Verschachtelung weiter zu erhöhenEine klassische visuelle Bugfix-Strategie ist, einen weiteren Container, Padding, Align und SizedBox hinzuzufügen, bis es richtig aussieht. Nach ein paar Durchläufen wird der Baum unlesbar.
Wenn ein Button nicht ausgerichtet ist, versuche zuerst, Wrapper zu entfernen, ein einzelnes Parent-Layout-Widget zu verwenden oder ein kleines Widget mit eigenen Constraints zu extrahieren.
Beispiel: ein Checkout-Screen, bei dem der Gesamtpreis beim Laden springt. Ein Assistent könnte die Preiszeile in mehr Widgets wrappen, um sie zu „stabilisieren“. Eine sauberere Lösung ist, Platz mit einem einfachen Loading-Placeholder zu reservieren und die Zeilenstruktur unverändert zu lassen.
Bevor du committest, mache einen Zwei-Minuten-Check, der den Nutzerwert prüft und dich vor Überraschungsregressionen schützt. Ziel ist nicht Perfektion, sondern sicherzustellen, dass diese Iteration leicht zu reviewen, zu testen und zurückzunehmen ist.
Lese die User Story einmal und prüfe dann diese Punkte gegen die laufende App (oder wenigstens gegen einen einfachen Widget-Test):
Eine schnelle Realität-Prüfung: Wenn du einen neuen Order-Details-Screen hinzugefügt hast, solltest du (1) ihn aus der Liste öffnen können, (2) einen Loading-Spinner sehen, (3) einen Fehler simulieren können, (4) einen leeren Auftrag sehen und (5) Back drücken, um ohne merkwürdige Sprünge zur Liste zurückzukehren.
Wenn dein Workflow Snapshots/Rollback unterstützt, erstelle vor größeren UI-Änderungen einen Snapshot. Plattformen wie Koder.ai bieten das an und es hilft, schneller zu iterieren, ohne den Main-Branch zu gefährden.
User Story: „Als Käufer kann ich Artikel durchstöbern, eine Detailseite öffnen, ein Item zu Favoriten speichern und später meine Favoriten ansehen." Ziel ist, in drei kleinen, überprüfbaren Schritten von Worten zu Screens zu kommen.
Iteration 1: Fokus nur auf die Browse-List. Erstelle einen Widget-Tree, der genug rendert, aber noch nicht an echte Daten gebunden ist: ein Scaffold mit AppBar, ein ListView mit Platzhalter-Rows und klare UIs für Loading und Empty. Halte den State einfach: loading (zeigt CircularProgressIndicator), empty (kurze Nachricht + Try again Button) und ready (zeigt die Liste).
Iteration 2: Füge die Detailseite und Navigation hinzu. Halte es explizit: onTap pusht eine Route und übergibt ein kleines Parameter-Objekt (z. B. item id, title). Starte die Details-Seite read-only mit Titel, Beschreibung-Platzhalter und einem Favorite-Action-Button. Ziel ist: list -> details -> back, ohne extra Flows.
Iteration 3: Führe Favorite-Status-Updates und UI-Feedback ein. Füge eine einzige Quelle der Wahrheit für Favoriten hinzu (auch wenn sie im Speicher bleibt) und verbinde sie mit beiden Screens. Tippen auf Favorite aktualisiert sofort das Icon und zeigt eine kleine Bestätigung (z. B. SnackBar). Dann füge einen Favorites-Screen hinzu, der denselben State liest und den Empty-State behandelt.
Ein reviewbares Diff sieht typischerweise so aus:
browse_list_screen.dart: Widget-Tree plus Loading/Empty/Ready UIitem_details_screen.dart: UI-Layout und akzeptiert Navigation-Parameterfavorites_store.dart: minimaler State-Holder und Update-Methodenapp_routes.dart: Routen und typisierte Navigation-Helperfavorites_screen.dart: liest State und zeigt Empty/List UIWenn eine Datei „der Ort, an dem alles passiert" wird, splitte sie, bevor du weitermachst. Kleine Dateien mit klaren Namen machen die nächste Iteration schnell und sicher.
Wenn der Workflow nur funktioniert, wenn du „im Flow" bist, bricht er zusammen, sobald du den Context wechselst oder ein Kollege am Feature arbeitet. Mache die Schleife zur Gewohnheit, indem du sie dokumentierst und Guardrails für die Änderungsgröße setzt.
Verwende eine Team-Vorlage, damit jede Iteration mit denselben Eingaben beginnt und dieselbe Art von Ausgabe liefert. Halte sie kurz, aber spezifisch:
Das reduziert die Wahrscheinlichkeit, dass der Assistent mitten im Feature neue Patterns erfindet.
Wähle eine Definition von klein, die im Code-Review leicht durchsetzbar ist. Zum Beispiel begrenze jede Iteration auf eine bestimmte Anzahl Dateien und trenne UI-Refactors von Verhalten.
Einfache Regeln:
Füge Checkpoints hinzu, damit du einen schlechten Schritt schnell rückgängig machen kannst. Mindestens tagge Commits oder halte lokale Checkpoints vor größeren Refactors. Wenn dein Workflow Snapshots/Rollback unterstützt, nutze sie großzügig.
Wenn du einen chatbasierten Workflow willst, der Flutter-Apps end-to-end generieren und verfeinern kann, bietet Koder.ai einen Planungsmodus, der dir hilft, einen Plan und die erwarteten Dateiveränderungen vor dem Anwenden zu prüfen.
Verwende zuerst eine kleine, testbare UI-Spezifikation. Schreibe 3–6 Zeilen, die abdecken:
Baue dann nur diesen Slice (meist ein Screen + 1–2 Widgets).
Wandle die Story in vier Bereiche um:
Wenn du die Akzeptanzüberprüfung nicht schnell beschreiben kannst, ist die Story noch zu vage für einen sauberen UI-Diff.
Beginne mit der Generierung nur einer Widget-Tree-Outline (Namen + Hierarchie + was jeder Teil zeigt). Kein Code.\n\nFordere dann eine Aufschlüsselung der Komponentenverantwortung an (was jedes Widget macht).\n\nErst danach generiere das stateless Scaffold mit expliziten Inputs (Werte + Callbacks) und mache Styling in einem separaten Schritt.
Behandle es als harte Regel: eine Intention pro Iteration.\n\n- Iteration A: Widget-Tree/Layout\n- Iteration B: State-Anbindung\n- Iteration C: Navigation\n\nWenn ein Commit Layout, State und Routen zusammen ändert, wissen Reviewer nicht, was den Bug verursacht hat, und ein Rollback wird kompliziert.
Halte Widgets „dumm“: sie rendern State, sie entscheiden nicht über Geschäftslogik.\n\nEin praktisches Default:
Vermeide asynchrone Aufrufe in build()—das führt zu wiederholten Aufrufen bei Rebuilds.
Definiere Zustände und Übergänge in normalem Text bevor du kodierst.\n\nBeispielmuster:
Schreibe eine kleine Flusskarte für die Story:\n\n- Entry: woher der Nutzer kommt\n- Next: der wichtigste Vorwärtsschritt\n- Cancel: wohin bei Abbruch\n- Back: was rückwärts erhalten oder verworfen wird\n- Fallback: was passiert, wenn Daten fehlen\n Sperre außerdem, welche Parameter zwischen Screens übergeben werden (IDs, Filter, Draft-Daten), damit du nicht Kontext in Globals versteckst.
Bevorzuge eine feature-first Ordnerstruktur, damit Änderungen lokal bleiben. Zum Beispiel:\n
lib/features/<feature>/screens/\n- lib/features/<feature>/widgets/\n- lib/features/<feature>/state/\n- lib/features/<feature>/routes.dart\n
Halte jede Iteration auf einen Feature-Ordner fokussiert und vermeide nebenbei Refactors an anderen Stellen.Eine einfache Regel: stabilisiere die Schnittstellen, nicht die Interna.\n\n- Halte öffentliche Widget-Props klein und typisiert\n- Übergib Werte + Callbacks statt globalen State zu lesen\n- Route-Argumente explizit halten (oft ein einzelnes Args-Objekt)
Reviewer interessieren sich vor allem dafür, dass Inputs/Outputs stabil bleiben, auch wenn das Layout sich verändert.
Mache einen schnellen Zwei-Minuten-Check:\n\n- Kannst du loading, empty, error, success auslösen und sehen, dass sie annehmbar aussehen?\n- Führt Back zurück, wo erwartet (keine seltsamen Sprünge)?\n- Hast du nur eine kleine Menge Dateien geändert mit klarer Verantwortlichkeit?\n- Gibt es temporäre Flags/Platzhalter, die später Probleme bereiten könnten?\n Wenn möglich (Snapshots/Rollbacks), nimm vor größeren Layout-Refactors einen Snapshot, damit du leicht zurückrollen kannst.