Claude Code dla iteracji UI we Flutterze: praktyczna pętla, która przekształca user stories w drzewa widgetów, stan i nawigację, utrzymując zmiany modularne i łatwe do przeglądu.

Szybka praca nad UI we Flutterze często dobrze się zaczyna. Poprawiasz układ, dodajesz przycisk, przesuwasz pole i ekran szybko staje się lepszy. Kłopoty pojawiają się po kilku rundach, gdy szybkość zamienia się w stos zmian, których nikt nie chce przeglądać.
Zespoły zwykle trafiają na te same porażki:
Dużą przyczyną jest podejście „jeden wielki prompt”: opisz całą funkcję, poproś o pełny zestaw ekranów i zaakceptuj duży output. Asystent próbuje pomóc, ale dotyka zbyt wielu części kodu naraz. To sprawia, że zmiany są niechlujne, trudne do przejrzenia i ryzykowne do scalania.
Powtarzalna pętla to naprawia — wymusza jasność i ogranicza promień zmian. Zamiast „zbuduj funkcję”, rób to powtarzalnie: wybierz jedną historię użytkownika, wygeneruj najmniejszy fragment UI, który ją udowodni, dodaj tylko stan potrzebny dla tego fragmentu, a potem podłącz nawigację dla jednej ścieżki. Każda iteracja pozostaje na tyle mała, że można ją przeglądnąć, a błędy łatwo cofnąć.
Celem jest praktyczny proces przekształcania user stories w konkretne ekrany, obsługę stanu i przepływy nawigacji bez utraty kontroli. Zrobione dobrze, otrzymujesz modułowe kawałki UI, mniejsze dify i mniej niespodzianek przy zmianie wymagań.
User stories są pisane dla ludzi, nie dla drzew widgetów. Zanim cokolwiek wygenerujesz, zamień historię w mały spec UI opisujący widoczne zachowanie. „Gotowe” powinno być testowalne: co użytkownik widzi, klika i potwierdza, a nie czy design „wygląda nowocześnie”.
Prosty sposób, by utrzymać zakres konkretny, to rozbicie historii na cztery kubełki:
Jeśli historia wciąż jest niejasna, odpowiedz prostym językiem na te pytania:
Dodaj ograniczenia wcześnie, bo one kierują każdą decyzją o układzie: podstawy tematu (kolory, odstępy, typografia), responsywność (najpierw telefon w orientacji pionowej, potem szerokości tabletów) i minimalne wymagania dostępności, jak rozmiar celów dotykowych, skalowanie tekstu i znaczące etykiety ikon.
Wreszcie zdecyduj, co jest stabilne, a co elastyczne, żeby nie powodować churnu w kodzie. Stabilne elementy to rzeczy, od których zależą inne funkcje, jak nazwy tras, modele danych i istniejące API. Elementy elastyczne to bezpieczniejsze do iteracji rzeczy, jak struktura układu, mikrotekst i dokładny skład widgetów.
Przykład: „Jako użytkownik, mogę zapisać element do Ulubionych z ekranu szczegółów.” Budowalny spec UI może wyglądać tak:
To wystarczy, żeby zbudować, zrecenzować i iterować bez zgadywania.
Małe dify nie oznaczają pracy wolniej. Sprawiają, że każda zmiana UI jest łatwa do sprawdzenia, łatwa do odwrócenia i trudna do zepsucia. Najprostsza zasada: jedna zmianа ekranu lub jedna interakcja na iterację.
Wybierz ciasny wycinek przed startem. „Dodaj stan pusty do ekranu Zamówienia” to dobry wycinek. „Przeprojektuj cały flow Zamówień” już nie. Celuj w diff, który kolega z zespołu zrozumie w minutę.
Stabilna struktura folderów też pomaga trzymać zmiany w ryzach. Prosty, feature-first układ zapobiega rozrzucaniu widgetów i tras po całej aplikacji:
lib/
features/
orders/
screens/
widgets/
state/
routes.dart
Trzymaj widgety małe i złożone. Gdy widget ma jasne wejścia i wyjścia, możesz zmieniać układ bez dotykania logiki stanu i zmieniać stan bez przepisywania UI. Preferuj widgety, które przyjmują zwykłe wartości i callbacki, a nie globalny stan.
Pętla, która pozostaje przeglądalna:
Ustal twardą regułę: każda zmiana musi dać się łatwo cofnąć lub odizolować. Unikaj przypadkowych refactorów podczas iterowania nad ekranem. Jeśli zauważysz niezwiązany problem, zapisz go i napraw w osobnym commicie.
Jeśli twoje narzędzie wspiera snapshoty i rollback, używaj każdego wycinka jako punktu snapshotu. Niektóre platformy vibe-coding, takie jak Koder.ai, oferują snapshoty i rollback, co może ułatwić eksperymenty przy odważnych zmianach UI.
Jeszcze jedna praktyka, która utrzymuje wczesne iteracje w ryzach: preferuj dodawanie nowych widgetów zamiast edytowania współdzielonych. Komponenty współdzielone to miejsca, gdzie małe zmiany stają się dużymi difami.
Szybka praca nad UI jest bezpieczna, gdy oddzielisz myślenie od pisania. Zacznij od planu drzewa widgetów przed generowaniem kodu.
Poproś tylko o szkic drzewa widgetów. Chcesz nazwy widgetów, hierarchię i co każda część pokazuje. Bez kodu. Tutaj wychwytujesz brakujące stany, puste ekrany i dziwne wybory układu, gdy wszystko jest jeszcze tanie do zmiany.
Poproś o rozbiórkę komponentów z przypisaniem odpowiedzialności. Trzymaj każdy widget skupiony: jeden renderuje nagłówek, inny listę, jeszcze inny obsługuje UI dla pustego/błędu. Jeśli coś będzie potrzebować stanu później, zanotuj to teraz, ale jeszcze tego nie implementuj.
Wygeneruj szkic ekranu i stateless widgety. Zacznij od jednego pliku ekranu z placeholderami i jasnymi TODO. Trzymaj wejścia explicite (parametry konstruktora), żeby później można było podłączyć prawdziwy stan bez przepisywania drzewa.
Zrób oddzielny przebieg dla stylowania i szczegółów układu: odstępy, typografia, theming i responsywność. Traktuj stylowanie jako osobny diff, żeby recenzje pozostały proste.
Postaw ograniczenia na początku, by asystent nie wymyślał UI, którego nie możesz wypuścić:
Konkretny przykład: user story to „Jako użytkownik mogę przeglądać zapisane elementy i usuwać jeden”. Poproś o drzewo widgetów obejmujące app bar, listę z wierszami elementów i stan pusty. Następnie poproś o rozkład jak SavedItemsScreen, SavedItemTile, EmptySavedItems. Dopiero potem generuj szkic ze stateless widgetami i fikcyjnymi danymi, a finalnie dodaj styl (divider, padding, widoczny przycisk usuwania) w oddzielnym kroku.
Iteracja UI rozpada się, gdy każdy widget zaczyna podejmować decyzje. Trzymaj drzewo widgetów „głupim”: ma czytać stan i renderować, a nie zawierać reguł biznesowych.
Zacznij od nazwania stanów prostym językiem. Większość funkcji potrzebuje więcej niż „loading” i „done”:
Potem wypisz zdarzenia, które mogą zmieniać stan: tapy, wysłanie formularza, pull-to-refresh, back navigation, retry i „użytkownik edytował pole”. Zrobienie tego z góry zapobiega zgadywaniu później.
Wybierz jedno podejście do stanu dla danej funkcji i trzymaj się go. Celem nie jest „najlepszy wzorzec”, lecz spójne dify.
Dla małego ekranu prosty controller (np. ChangeNotifier lub ValueNotifier) często wystarcza. Umieść logikę w jednym miejscu:
Zanim dodasz kod, zapisz przejścia stanów prostym językiem. Przykład dla ekranu logowania:
"Gdy użytkownik tapnie Sign in: ustaw Loading. Jeśli email jest nieprawidłowy: pozostań w Partial input i pokaż komunikat inline. Jeśli hasło jest złe: ustaw Error z wiadomością i włącz Retry. Jeśli sukces: ustaw Success i nawiguj do Home."
Potem wygeneruj minimalny kod Dart odpowiadający tym zdaniom. Recenzje pozostają proste, bo możesz porównać diff do reguł.
Uczyń walidację explicite. Zdecyduj, co się dzieje, gdy pola są nieprawidłowe:
Gdy te odpowiedzi są zapisane, UI pozostaje czysty, a kod stanu mały.
Dobra nawigacja zaczyna się od małej mapy, nie od sterty tras. Dla każdej user story zapisz cztery momenty: gdzie użytkownik wchodzi, jaki jest najbardziej prawdopodobny następny krok, jak może anulować i co znaczy „back” (powrót do poprzedniego ekranu czy do bezpiecznego stanu domowego).
Prosta mapa tras powinna odpowiedzieć na pytania, które zwykle powodują prace:
Następnie zdefiniuj parametry przekazywane między ekranami. Bądź explicite: ID (productId, orderId), filtry (zakres dat, status) i dane robocze (częściowo wypełniony formularz). Jeśli pominiesz to, skończysz upychając stan do globalnych singletonów lub przebudowywaniem ekranów, by „odnaleźć” kontekst.
Deep linki mają znaczenie, nawet jeśli nie wdrażasz ich pierwszego dnia. Zdecyduj, co się dzieje, gdy użytkownik wyląduje w połowie flow: czy możesz załadować brakujące dane, czy przekierować do bezpiecznego wejścia?
Zdecyduj też, które ekrany powinny zwracać wyniki. Przykład: ekran „Select Address” zwraca addressId, a ekran checkout aktualizuje się bez pełnego odświeżenia. Trzymaj kształt zwracanego wyniku mały i typowany, by zmiany były łatwe do przeglądu.
Zanim zaczniesz kodować, wypunktuj przypadki brzegowe: niezapisane zmiany (pokaż dialog potwierdzenia), wymagane logowanie (pauzuj i wznow po logowaniu) i brak/usunięte dane (pokaż błąd i jasną drogę wyjścia).
Kiedy iterujesz szybko, prawdziwe ryzyko to nie „zły UI”, lecz nieprzeglądalny UI. Jeśli współpracownik nie potrafi powiedzieć, co się zmieniło, dlaczego i co pozostało stabilne, każda następna iteracja będzie wolniejsza.
Zasada pomocna: zablokuj interfejsy najpierw, potem pozwól wnętrznościom się zmieniać. Ustabilizuj publiczne propsy widgetów (wejścia), małe modele UI i argumenty tras. Gdy już są nazwane i typowane, możesz przekształcać drzewo widgetów bez łamania reszty aplikacji.
Poproś o plan przyjazny diffom przed generowaniem kodu. Chcesz plan, który mówi, które pliki się zmienią, a które muszą pozostać nietknięte. To utrzymuje recenzje skupione i zapobiega przypadkowym refactorom zmieniającym zachowanie.
Wzorce, które utrzymują dify małe:
Powiedzmy, że user story to „Jako kupujący, mogę edytować adres wysyłki z poziomu checkout”. Najpierw zablokuj args trasy: CheckoutArgs(cartId, shippingAddressId) pozostaje stabilny. Potem iteruj wewnątrz ekranu. Gdy układ się ustabilizuje, rozdziel na AddressForm, AddressSummary i SaveBar.
Jeśli obsługa stanu się zmienia (np. walidacja przechodzi z widgetu do CheckoutController), recenzja pozostaje czytelna: pliki UI głównie zmieniają renderowanie, a kontroler pokazuje logikę w jednym miejscu.
Najszybszy sposób na spowolnienie to poprosić asystenta o zmianę wszystkiego naraz. Jeśli jeden commit dotyka layout, stanu i nawigacji, recenzenci nie wiedzą, co zepsuło się i cofnięcie jest kłopotliwe.
Bezpieczniejsza praktyka to jedna intencja na iterację: ukształtuj drzewo widgetów, potem podłącz stan, potem połącz nawigację.
Jednym częstym problemem jest pozwolenie, by generowany kod wprowadzał nowy wzorzec na każdej stronie. Jeśli jedna strona używa Provider, druga setState, a trzecia wprowadza niestandardowy controller, aplikacja szybko staje się niespójna. Wybierz niewielki zestaw wzorców i trzymaj się go.
Inny błąd to umieszczanie pracy asynchronicznej bezpośrednio w build(). Może to wyglądać dobrze w szybkim demo, ale powoduje powtarzane wywołania przy rebuildach, migotanie i trudne do śledzenia błędy. Przenieś wywołanie do initState(), view modelu lub dedykowanego controllera i trzymaj build() skupione na renderowaniu.
Nazewnictwo to cicha pułapka. Kod, który kompiluje się, ale czyta się jak Widget1, data2 czy temp, utrudnia przyszłe refaktory. Jasne nazwy pomagają też asystentowi generować lepsze kolejna zmiany, bo intencja jest oczywista.
Zabezpieczenia, które zapobiegają najgorszym skutkom:
build()Klasyczna poprawka wizualna to dodanie kolejnego Container, Padding, Align i SizedBox, aż wygląda dobrze. Po kilku iteracjach drzewo staje się nieczytelne.
Jeśli przycisk jest źle wyrównany, najpierw spróbuj usunąć opakowania, użyć jednego nadrzędnego widgetu układu lub wyodrębnić mały widget z własnymi ograniczeniami.
Przykład: ekran checkout, gdzie suma zamówienia skacze podczas ładowania. Asystent mógłby owinąć wiersz ceny dodatkowymi widgetami, by go „stabilizować”. Czystsze rozwiązanie to zarezerwowanie miejsca prostym placeholderem podczas ładowania, pozostawiając strukturę wiersza bez zmian.
Zanim commitniesz, zrób dwuminutowy przegląd, który sprawdza wartość dla użytkownika i chroni przed niespodziewanymi regresjami. Celem nie jest perfekcja, lecz upewnienie się, że iteracja jest łatwa do przeglądu, przetestowania i cofnięcia.
Przeczytaj user story raz, potem zweryfikuj te elementy na działającej aplikacji (lub przynajmniej w prostym teście widgetu):
Krótki test rzeczywistości: jeśli dodałeś nowy ekran szczegółów zamówienia, powinieneś móc (1) otworzyć go z listy, (2) zobaczyć spinner ładowania, (3) zasymulować błąd, (4) zobaczyć pusty zamówienie i (5) nacisnąć back, by wrócić do listy bez dziwnych skoków.
Jeśli workflow wspiera snapshoty i rollback, zrób snapshot przed większym refactorem układu. Niektóre platformy, jak Koder.ai, to wspierają i pomagają iterować szybciej bez ryzyka dla głównej gałęzi.
User story: "Jako kupujący, mogę przeglądać przedmioty, otworzyć stronę szczegółów, zapisać przedmiot do ulubionych i później zobaczyć moje ulubione." Celem jest przejść od słów do ekranów w trzech małych, przeglądalnych krokach.
Iteracja 1: skup się tylko na liście przeglądania. Stwórz drzewo widgetów wystarczające do renderowania, ale nie związane z prawdziwymi danymi: Scaffold z AppBar, ListView z placeholderowymi wierszami i jasne UI dla loadingu i stanu pustego. Trzymaj stan prosty: loading (pokazuje CircularProgressIndicator), empty (krótka wiadomość i przycisk Spróbuj ponownie) i ready (pokazuje listę).
Iteracja 2: dodaj ekran szczegółów i nawigację. Trzymaj to explicite: onTap pushuje trasę i przekazuje mały obiekt parametrów (np. item id, title). Zacznij ekran szczegółów jako tylko do odczytu z tytułem, placeholderem opisu i przyciskiem akcji Favorite. Chodzi o dopasowanie historii: lista -> szczegóły -> back, bez dodatkowych flow.
Iteracja 3: wprowadź aktualizacje stanu ulubionych i feedback UI. Dodaj jedno źródło prawdy dla ulubionych (nawet jeśli zostanie w pamięci), podłącz je do obu ekranów. Tap Favorite natychmiast aktualizuje ikonę i pokazuje małe potwierdzenie (np. SnackBar). Następnie dodaj ekran Ulubione, który czyta ten sam stan i obsługuje stan pusty.
Przeglądalny diff zwykle wygląda tak:
browse_list_screen.dart: drzewo widgetów plus loading/empty/ready UIitem_details_screen.dart: układ UI i akceptuje parametry nawigacjifavorites_store.dart: minimalny holder stanu i metody aktualizacjiapp_routes.dart: trasy i typowane helpery nawigacyjnefavorites_screen.dart: czyta stan i pokazuje pusty/listę UIJeśli którykolwiek plik staje się „miejscem, gdzie dzieje się wszystko”, podziel go zanim pójdziesz dalej. Małe pliki z jasnymi nazwami przyspieszają kolejną iterację.
Jeśli workflow działa tylko wtedy, gdy jesteś „w strefie”, pęknie, gdy zmienisz ekran lub gdy współpracownik wejdzie w projekt. Uczyń pętlę nawykiem, zapisując ją i wprowadzając ograniczenia co do rozmiaru zmian.
Użyj jednego zespołowego szablonu, aby każda iteracja zaczynała się od tych samych danych wejściowych i dawała ten sam rodzaj outputu. Trzymaj go krótko, ale konkretnie:
To zmniejsza szansę, że asystent wymyśli nowe wzorce w połowie pracy nad funkcją.
Wybierz definicję „małe”, którą łatwo egzekwować w code review. Na przykład ogranicz każdą iterację do zdefiniowanej liczby plików i oddziel refactory UI od zmian zachowania.
Prosty zestaw reguł:
Dodaj punkty kontrolne, żeby móc szybko cofnąć zły krok. Przynajmniej taguj commity lub trzymaj lokalne checkpointy przed większymi refactorami. Jeśli workflow wspiera snapshoty i rollback, używaj ich agresywnie.
Jeśli chcesz workflow czatowy, który może generować i udoskonalać aplikacje Flutter end-to-end, Koder.ai zawiera tryb planowania, który pomaga przeglądać plan i oczekiwane zmiany w plikach przed ich zastosowaniem.
Użyj małego, testowalnego specyfikatu UI najpierw. Napisz 3–6 linijek, które obejmują:
Następnie zbuduj tylko ten wycinek (często jeden ekran + 1–2 widgety).
Przekształć historię w cztery koszyki:
Jeśli nie potrafisz szybko opisać kryterium akceptacji, historia jest wciąż zbyt niejasna dla czystej diff-owej pracy.
Zacznij od wygenerowania tylko zarysu drzewa widgetów (nazwy + hierarchia + co każda część pokazuje). Żaden kod.
Potem poproś o rozkład odpowiedzialności komponentów (co każdy widget robi).
Dopiero potem wygeneruj statelessowy szkic z wyraźnymi wejściami (wartości + callbacki), a stylowanie zrób w osobnym kroku.
Traktuj to jako twardą zasadę: jedna intencja na iterację.
Jeśli jeden commit zmienia layout, stan i trasy razem, recenzenci nie będą wiedzieć, co spowodowało błąd, a cofnięcie będzie trudne.
Utrzymuj widgety „głupie”: powinny renderować stan, a nie podejmować reguł biznesowych.
Praktyczny domyślny wzorzec:
Unikaj wywołań asynchronicznych w — powodują powtarzane wezwania przy rebuildach.
Zdefiniuj stany i przejścia słowami, zanim zaczniesz kodować.
Przykładowy wzorzec:
Następnie wypisz zdarzenia przesuwające między nimi (refresh, retry, submit, edit). Kod łatwiej porównać z zapisanymi regułami.
Napisz małą mapę przepływu dla historii:
Domyślaj się do feature-first folderów, żeby zmiany pozostały zawężone. Na przykład:
lib/features/<feature>/screens/lib/features/<feature>/widgets/lib/features/<feature>/state/lib/features/<feature>/routes.dartPotem trzymaj każdą iterację skoncentrowaną na jednym folderze funkcji i unikaj przypadkowych refactorów poza nim.
Prosta zasada: ustabilizuj interfejsy, nie implementacje.
Recenzenci najbardziej dbają, aby wejścia/wyjścia pozostały stabilne, nawet jeśli układ się zmienia.
Zrób dwuminutowy przegląd:
Jeśli możesz, zrób migawkę/snapshot przed większym refactorem, by móc łatwo wrócić.
build()Zablokuj też, co jest przekazywane między ekranami (ID, filtry, drafty), by nie chować kontekstu w globalach.