Claude Code för Flutter UI-iteration: en praktisk loop för att förvandla user stories till widget-träd, state och navigation samtidigt som ändringar hålls modulära och lätta att granska.

Snabbt Flutter-arbete börjar ofta bra. Du finjusterar en layout, lägger till en knapp, flyttar ett fält och skärmen blir bättre snabbt. Problemet visar sig efter några rundor, när hastighet blir en hög med ändringar som ingen vill granska.
Team stöter vanligtvis på samma fel:
En stor orsak är "one big prompt"-metoden: beskriv hela funktionen, be om alla skärmar och acceptera ett stort svar. Assistenten försöker vara hjälpsam, men rör för många delar av koden på en gång. Det gör ändringarna stökiga, svåra att granska och riskfyllda att merge:a.
En upprepbar loop löser detta genom att tvinga fram tydlighet och begränsa blast radius. Istället för "bygg funktionen" gör du upprepade gånger: välj en användarstory, generera den minsta UI-skivan som bevisar den, lägg bara till den state som behövs för den skivan, och koppla sedan navigeringen för en väg. Varje pass förblir tillräckligt litet för att kunna granskas, och misstag är lätta att rulla tillbaka.
Målet här är ett praktiskt arbetsflöde för att förvandla user stories till konkreta skärmar, state-hantering och navigationsflöden utan att tappa kontrollen. Gjort väl får du modulära UI-delar, mindre diffs och färre överraskningar när krav ändras.
User stories är skrivna för människor, inte widget-träd. Innan du genererar något, omvandla storyn till en liten UI-spec som beskriver synligt beteende. "Färdig" ska vara testbart: vad användaren kan se, trycka på och bekräfta, inte om designen "känns modern".
Ett enkelt sätt att hålla scope konkret är att dela upp storyn i fyra fack:
Om storyn fortfarande känns vag, svara på dessa frågor i vanligt språk:
Lägg till begränsningar tidigt eftersom de styr varje layoutval: temat (färger, spacing, typografi), responsivitet (telefon porträtt först, sedan tablet-bredd), och tillgänglighetsminimum som tryckyta, läsbara textskalningar och meningsfulla labels för ikoner.
Till sist, bestäm vad som är stabilt kontra flexibelt så du inte churn: stabilt är saker andra funktioner beror på, som rout-namn, datamodeller och befintliga API:er. Flexibla saker är säkrare att iterera på, som layoutstruktur, microcopy och exakt widget-sammansättning.
Exempel: "Som användare kan jag spara ett objekt till Favoriter från detaljskärmen." En byggbar UI-spec kan vara:
Det är tillräckligt för att bygga, granska och iterera utan gissningar.
Små diffs handlar inte om att jobba långsammare. De gör varje UI-ändring lätt att granska, enkel att ångra och svår att förstöra. Den enklaste regeln: en skärm eller en interaktion per iteration.
Välj en snäv skiva innan du börjar. "Lägg till ett empty state på Orders-skärmen" är en bra skiva. "Göra om hela Orders-flödet" är det inte. Sikta på en diff som en kollega kan förstå på en minut.
En stabil mappstruktur hjälper också att hålla ändringar inneslutna. En enkel feature-first-layout förhindrar att widgets och routes sprids över appen:
lib/
features/
orders/
screens/
widgets/
state/
routes.dart
Håll widgets små och komponerade. När en widget har tydliga inputs och outputs kan du ändra layout utan att röra state-logik, och ändra state utan att skriva om UI. Föredra widgets som tar rena värden och callbacks, inte global state.
En loop som förblir granskbar:
Sätt en hård regel: varje ändring måste vara lätt att återställa eller isolera. Undvik drive-by-refactors medan du itererar på en skärm. Om du märker orelaterade problem, skriv ner dem och fixa dem i en separat commit.
Om ditt verktyg stödjer snapshots och rollback, använd varje skiva som en snapshot-punkt. Vissa vibe-coding-plattformar som Koder.ai inkluderar snapshots och rollback, vilket kan göra experiment säkrare när du provar djärva UI-ändringar.
En vana som håller tidiga iterationer lugna: föredra att lägga till nya widgets framför att redigera delade sådana. Delade komponenter är där små ändringar blir till stora diffs.
Snabbt UI-arbete är säkert när du separerar tänkande från skrivande. Börja med en tydlig widget-träd-plan innan du genererar kod.
Be endast om en widget-trädöversikt. Du vill ha widget-namn, hierarki och vad varje del visar. Ingen kod ännu. Här fångar du saknade states, empty-skärmar och konstiga layoutval medan allt fortfarande är billigt att ändra.
Be om en komponentuppdelning med ansvar. Håll varje widget fokuserad: en widget renderar headern, en annan renderar listan, en tredje hanterar empty/error UI. Om något behöver state senare, notera det nu men implementera det inte än.
Generera screen-scaffold och stateless widgets. Börja med en enda skärmsfil med platshållarinnehåll och tydliga TODOs. Håll inputs explicita (konstruktörsparametrar) så du kan ansluta riktig state senare utan att skriva om trädet.
Gör en separat pass för styling och layoutdetaljer: spacing, typografi, theming och responsivt beteende. Behandla styling som en egen diff så granskningar förblir enkla.
Sätt begränsningar i början så assistenten inte hittar på UI du inte kan skicka:
Konkreta exempel: user storyn är "As a user, I can review my saved items and remove one." Be om ett widget-träd som inkluderar en app bar, en lista med item-rows och ett empty state. Begär sedan en uppdelning som SavedItemsScreen, SavedItemTile, EmptySavedItems. Först efter det genererar du scaffolden med stateless widgets och falska data, och slutligen lägger du till styling (divider, padding och en tydlig remove-knapp) i en separat pass.
UI-iteration går sönder när varje widget börjar fatta beslut. Håll widget-trädet dumb: det ska läsa state och rendera, inte innehålla affärsregler.
Börja med att namnge states i klart språk. De flesta features behöver mer än "loading" och "done":
Lista sedan events som kan ändra state: taps, formulärsubmit, pull-to-refresh, back navigation, retry och "user edited a field." Att göra detta i förväg förhindrar gissningar senare.
Välj en state-approach för featuren och håll dig till den. Målet är inte "det bästa mönstret", utan konsekventa diffs.
För en liten skärm räcker ofta en enkel controller (som ChangeNotifier eller ValueNotifier). Placera logiken på ett ställe:
Innan du lägger till kod, skriv state-övergångarna i klart språk. Exempel för en inloggningsskärm:
"När användaren trycker Sign in: sätt Loading. Om email är ogiltig: stanna i Partial input och visa ett inline-meddelande. Om lösenordet är fel: sätt Error med ett meddelande och möjliggör Retry. Vid success: sätt Success och navigera till Home."
Generera sedan minimal Dart-kod som matchar de meningarna. Granskningar förblir enkla eftersom diffen kan jämföras med reglerna.
Gör validering explicit. Bestäm vad som händer när inputs är ogiltiga:
När dessa svar finns nedskrivna förblir UI:n ren och state-koden liten.
Bra navigation börjar som en liten karta, inte en hög med routes. För varje user story skriv ner fyra ögonblick: var användaren kommer in, nästa mest sannolika steg, hur de avbryter och vad "back" betyder (till föregående skärm eller till ett säkert home state).
En enkel route-karta bör besvara frågor som ofta orsakar omarbete:
Definiera sedan parametrarna som skickas mellan skärmar. Var explicit: IDs (productId, orderId), filter (datumintervall, status) och draft-data (delvis ifyllt formulär). Om du hoppar över detta kommer du stoppa state i globala singletons eller bygga om skärmar för att "hitta" kontext.
Deep links spelar roll även om du inte levererar dem dag 1. Bestäm vad som händer när en användare landar mitt i ett flöde: kan du ladda saknad data, eller bör du omdirigera till en säker entry-screen?
Bestäm också vilka skärmar som ska returnera resultat. Exempel: en "Select Address"-skärm returnerar en addressId, och checkout-skärmen uppdateras utan full refresh. Håll return-objektet litet och typat så förändringar förblir lätta att granska.
Innan kod, peka ut edge cases: osparade ändringar (visa en confirm-dialog), auth required (pausa och återuppta efter login), och saknad eller borttagen data (visa fel och en tydlig utväg).
När du itererar snabbt är den verkliga risken inte "fel UI." Det är ogranskarbar UI. Om en kollega inte kan se vad som ändrats, varför och vad som är stabilt, blir varje nästa iteration långsammare.
En regel som hjälper: lås gränssnitten först, tillåt sedan intern förflyttning. Stabilsera publika widget-props (inputs), små UI-modeller och route-argument. När de är namngivna och typade kan du forma om widget-trädet utan att bryta resten av appen.
Be om en diff-vänlig plan innan du genererar kod. Du vill ha en plan som säger vilka filer som ändras och vilka som måste förbli orörda. Det håller granskningar fokuserade och förhindrar oavsiktliga refactors som ändrar beteende.
Mönster som håller diffs små:
Säg user storyn är "As a shopper, I can edit my shipping address from checkout." Lås route-args först: CheckoutArgs(cartId, shippingAddressId) förblir stabilt. Iterera sedan inuti skärmen. När layouten lugnat sig, dela upp i AddressForm, AddressSummary och SaveBar.
Om state-hantering förändras (t.ex. validering flyttas från widget in i en CheckoutController), förblir granskningen läsbar: UI-filer ändrar mest rendering, medan controllern visar logikändringen på ett ställe.
Det snabbaste sättet att sakta ner är att be assistenten ändra allt på en gång. Om en commit rör layout, state och navigation, kan granskare inte avgöra vad som orsakade ett fel. Rollback blir rörigt.
En säkrare vana är en intent per iteration: forma widget-trädet, koppla sedan state, och slutligen koppla navigation.
Ett vanligt problem är att låta genererad kod uppfinna ett nytt mönster på varje sida. Om en sida använder Provider, nästa använder setState, och den tredje introducerar en custom controller-klass blir appen inkonsekvent snabbt. Välj ett litet set mönster och håll dig till dem.
Ett annat misstag är att lägga asynkront arbete direkt i build(). Det kan se okej ut i en snabb demo, men triggar upprepade anrop vid rebuilds, flicker och svårspårade buggar. Flytta anropet till initState(), en view model eller en dedikerad controller och håll build() fokuserad på rendering.
Namngivning är en tyst fälla. Kod som kompilerar men heter Widget1, data2 eller temp gör framtida refactors smärtsamma. Klara namn hjälper också assistenten att producera bättre uppföljningsändringar eftersom avsikten blir tydlig.
Guardrails som förhindrar de värsta utfallen:
build()En klassisk visuell fix är att lägga till fler Container, Padding, Align och SizedBox tills det ser rätt ut. Efter några pass blir trädet oläsligt.
Om en knapp är felplacerad, försök först ta bort wrappers, använd en enda föräldralayout eller extrahera en liten widget med egna constraints.
Exempel: en checkout-skärm där totalpriset hoppar vid laddning. En assistent kan wrappa prisraden i fler widgets för att "stabilize" den. En renare fix är att reservera utrymme med en enkel loading-platshållare samtidigt som radstrukturen behålls.
Innan du commit:ar, gör en tvåminutersgenomgång som kontrollerar användarvärde och skyddar mot överraskningar. Målet är inte perfektion, utan att iterationen är lätt att granska, testa och ångra.
Läs user storyn en gång, och verifiera dessa punkter mot den körande appen (eller åtminstone mot ett enkelt widget-test):
En snabb verklighetskontroll: om du la till en ny Order-detaljskärm bör du kunna (1) öppna den från listan, (2) se en loading-spinner, (3) simulera ett fel, (4) se en tom order och (5) trycka back för att återvända till listan utan konstiga hopp.
Om ditt arbetsflöde stödjer snapshots och rollback, ta en snapshot innan större UI-ändringar. Några plattformar som Koder.ai stödjer detta och kan hjälpa dig iterera snabbare utan att riskera main-branchen.
User story: "As a shopper, I can browse items, open a details page, save an item to favorites, and later view my favorites." Målet är att gå från ord till skärmar i tre små, granskbara steg.
Iteration 1: fokusera bara på browse-listan. Skapa ett widget-träd tillräckligt för att rendera men inte kopplat till riktig data: en Scaffold med en AppBar, en ListView med platshållarrader och tydlig UI för loading och empty states. Håll state enkel: loading (visa en CircularProgressIndicator), empty (visa ett kort meddelande och kanske en Try again-knapp), och ready (visa listan).
Iteration 2: lägg till detaljskärmen och navigation. Håll det explicit: onTap pushar en route och skickar ett litet parameterobjekt (t.ex. item id, title). Starta detaljsidan som read-only med en titel, en beskrivningsplatshållare och en Favorite-action-knapp. Poängen är att matcha storyn: listan -> detaljer -> back, utan extra flöden.
Iteration 3: inför favorites-state-uppdateringar och UI-feedback. Lägg till en single source of truth för favorites (även om den fortfarande är in-memory) och koppla den till båda skärmarna. Tryck på Favorite uppdaterar ikonen omedelbart och visar en liten bekräftelse (t.ex. en SnackBar). Lägg sedan till en Favorites-skärm som läser samma state och hanterar empty state.
En granskbar diff ser typiskt ut så här:
browse_list_screen.dart: widget-träd plus loading/empty/ready UIitem_details_screen.dart: UI-layout och tar emot navigation paramsfavorites_store.dart: minimal state-holder och uppdateringsmetoderapp_routes.dart: routes och typade navigationshjälparefavorites_screen.dart: läser state och visar empty/list UIOm någon fil blir "stället där allt händer", dela upp den innan du går vidare. Små filer med tydliga namn håller nästa iteration snabb och säker.
Om arbetsflödet bara fungerar när du är "in the zone" kommer det brytas när du byter skärm eller en kollega rör featuren. Gör loopen till en vana genom att skriva ner den och sätta guardrails runt change-storlek.
Använd en team-template så varje iteration börjar med samma inputs och producerar samma typ av output. Håll den kort men specifik:
Det minskar risken att assistenten hittar på nya mönster mitt i en feature.
Välj en definition av liten som är lätt att upprätthålla i kodgranskning. Till exempel: begränsa varje iteration till ett antal filer, och separera UI-refactors från beteendeförändringar.
Ett enkelt regelsätt:
Lägg till checkpoints så du snabbt kan ångra ett dåligt steg. Minst, tagga commits eller behåll lokala checkpoints innan större refactors. Om ditt arbetsflöde stödjer snapshots och rollback, använd dem flitigt.
Om du vill ha ett chattbaserat arbetsflöde som kan generera och förfina Flutter-appar end-to-end, inkluderar Koder.ai ett planning-läge som hjälper dig granska en plan och förväntade filändringar innan de appliceras.
Använd först en kort, testbar UI-spec. Skriv 3–6 rader som täcker:
Bygg sedan bara det snittet (ofta en skärm + 1–2 widgets).
Konvertera storyn till fyra kategorier:
Om du inte snabbt kan beskriva acceptance-checken är storyn fortfarande för vag för en ren UI-diff.
Börja med att generera endast en widget-träd-översikt (namn + hierarki + vad varje del visar). Ingen kod.
Be sedan om en ansvarsuppdelning per komponent (vad varje widget ansvarar för).
Först därefter, generera ett stateless scaffold med tydliga inputs (värden + callbacks) och gör styling i en separat pass.
Behandla det som en hård regel: en avsikt per iteration.
Om en enda commit ändrar layout, state och routes samtidigt vet inte granskaren vad som orsakade ett fel, och rollback blir rörigt.
Håll widgets “dumma”: de ska rendera state, inte fatta affärsbeslut.
Ett praktiskt standardupplägg:
Undvik asynkrona anrop i —det leder till upprepade anrop vid rebuild.
Definiera states och övergångar i vanligt språk innan du kodar.
Exempelmönster:
Lista sedan events som flyttar mellan dem (refresh, retry, submit, edit). Koden blir lättare att jämföra med de skrivna reglerna.
Skriv en liten “flow map” för storyn:
Standardisera på feature-first-mappar så ändringar förblir isolerade. Exempel:
lib/features/<feature>/screens/lib/features/<feature>/widgets/lib/features/<feature>/state/lib/features/<feature>/routes.dartHåll varje iteration fokuserad på en feature-mapp och undvik drive-by-refactors andra ställen.
En enkel regel: stabilisera gränssnitten, inte internals.
Granskare bryr sig mest om att inputs/outputs är stabila även om layouten förändras.
Gör en snabb tvåminuterskoll:
Om möjligt, ta en snapshot innan större layoutrefactors så du kan återställa enkelt.
build()Lås också vad som skickas mellan skärmar (IDs, filter, draft-data) så du inte hamnar med kontext i globala singletons.