Claude Code로 Flutter UI 반복하기: 실무 워크플로
Claude Code로 Flutter UI를 반복하는 실무 루프: 사용자 스토리를 위젯 트리, 상태, 네비게이션으로 바꾸되 변경을 모듈식으로 유지하고 리뷰하기 쉽게 만드는 방법.

문제: 빠른 UI 반복이 난장판으로 바뀌지 않게
빠른 Flutter UI 작업은 보통 잘 시작합니다. 레이아웃을 조정하고 버튼을 추가하고 필드를 옮기면 화면이 금세 좋아집니다. 문제는 몇 번의 반복 후에 속도가 변경사항이 너무 많아 리뷰하기 싫어지는 수준으로 변할 때 나타납니다.
팀들이 자주 겪는 실패들:
- 위젯 트리가 계획 없이 커져서 작은 변경도 여러 파일을 건드리게 됩니다.
- 상태가 UI 코드에 붙어 버려 재빌드가 예측 불가능해지고 버그 추적이 어려워집니다.
- 네비게이션 로직이 여기저기 흩어져 흐름이 실제 사용자 이동 방식과 맞지 않게 됩니다.
- 네이밍이 흐려지고 컴포넌트가 중복되며 어떤 위젯이 "진짜"인지 확신하기 힘듭니다.
- Diff가 거대해져서 리뷰어가 훑어보기만 하고 이슈가 지나가며 회귀가 나중에 드러납니다.
큰 원인 중 하나는 "한 번에 다 묻기(one big prompt)" 접근법입니다: 전체 기능을 설명하고 전체 스크린 세트를 요청해 큰 출력을 받아들이는 방식. 어시스턴트는 도우려 하지만 너무 많은 부분을 한꺼번에 건드려 변경이 엉망이 되고 리뷰하기 어렵고 병합하기 위험해집니다.
반복 가능한 루프는 폭발 반경을 제한하고 명확성을 강제함으로써 이 문제를 해결합니다. "기능을 다 만들어라" 대신 반복적으로 이렇게 하세요: 하나의 사용자 스토리를 골라 그것을 증명할 수 있는 가장 작은 UI 조각을 생성하고, 그 조각에 필요한 상태만 추가한 뒤 한 경로의 네비게이션만 연결합니다. 각 패스는 리뷰하기에 충분히 작게 유지되어 실수는 롤백하기 쉽습니다.
목표는 사용자 스토리를 구체적인 화면, 상태 처리, 네비게이션 흐름으로 바꾸되 통제력을 잃지 않는 실용적 워크플로를 제시하는 것입니다. 잘하면 모듈식 UI 조각, 더 작은 Diff, 요구사항 변경 시 적은 놀라움을 얻을 수 있습니다.
사용자 스토리를 빌드 가능한 UI 스펙으로 바꾸기
사용자 스토리는 사람을 위해 쓰입니다. 위젯 트리를 위해 쓰인 것이 아닙니다. 생성하기 전에 스토리를 작은 UI 스펙으로 변환하세요. "완료"는 테스트 가능해야 합니다: 사용자가 무엇을 보고, 탭하고, 확인할 수 있는지지 디자인이 "모던하게 느껴진다"는 평가가 아닙니다.
범위를 구체적으로 유지하는 간단한 방법은 스토리를 네 가지 버킷으로 나누는 것입니다:
- 스크린: 무엇이 바뀌고 무엇이 그대로인지.
- 컴포넌트: 어떤 새 UI 조각이 나타나고 어디에 위치하는가.
- 상태: 로딩, 성공, 오류, 빈 상태와 각각이 무엇을 보여주는가.
- 이벤트: 탭, 스와이프, 당겨서 새로고침, 뒤로 네비게이션, 재시도.
스토리가 여전히 애매하면 다음 질문들에 평범한 언어로 답하세요:
- 어떤 스크린이 바뀌고, 어떤 스크린은 그대로인가?
- 어떤 새 컴포넌트가 나타나고 어디에 속하는가?
- 어떤 상태들이 존재하고 각각은 무엇을 보여주는가?
- 어떤 이벤트가 상태를 바꾸는가?
- 앱을 실행한 뒤 30초 안에 할 수 있는 수락 체크는 무엇인가?
초기 제약을 조기에 정하세요. 제약은 모든 레이아웃 선택을 안내합니다: 테마 기본(색상, 간격, 타이포그래피), 반응성(모바일 세로 우선, 이후 태블릿), 접근성 최소 기준(탭 대상 크기, 읽기 가능한 텍스트 배율, 의미 있는 아이콘 레이블).
마지막으로 무엇이 안정적이고 무엇이 유연한지 결정해 코드베이스의 지속적인 소모(churn)를 막으세요. 안정적인 항목은 다른 기능들이 의존하는 것(라우트 이름, 데이터 모델, 기존 API)이고, 유연한 항목은 레이아웃 구조, 마이크로 카피, 정확한 위젯 구성 같은 안전하게 반복 가능한 것들입니다.
예시: "사용자는 상세 화면에서 항목을 즐겨찾기에 저장할 수 있다." 빌드 가능한 UI 스펙은 다음과 같습니다:
- 상세 화면에 북마크 아이콘이 보인다.
- 탭하면 저장 상태가 토글된다.
- 저장 중에는 작은 진행 표시기가 보인다.
- 실패 시 인라인 에러와 재시도 액션을 보여준다.
- 네비게이션은 그대로(새 라우트 없음).
이 정도면 추측 없이 빌드하고 리뷰하고 반복하기에 충분합니다.
반복 루프 설정: Diff를 작게 유지하기
작은 Diff는 느리게 작업하는 것이 아닙니다. 각 UI 변경을 리뷰하기 쉽고, 되돌리기 쉽고, 깨뜨리기 어렵게 만듭니다. 가장 간단한 규칙: 한 번의 반복에 한 화면 또는 한 상호작용만 변경하세요.
시작하기 전에 촘촘한 슬라이스를 선택하세요. "Orders 화면에 빈 상태 추가"는 좋은 슬라이스입니다. "Orders 흐름 전체 재작업"은 아닙니다. 동료가 1분 만에 이해할 수 있는 Diff를 목표로 하세요.
안정된 폴더 구조도 변경을 한정하는 데 도움이 됩니다. 단순한 기능 우선 레이아웃은 위젯과 라우트를 앱 전역에 흩어지게 하지 않습니다:
lib/
features/
orders/
screens/
widgets/
state/
routes.dart
위젯을 작고 조합 가능하게 유지하세요. 위젯이 명확한 입력과 출력을 가지면 상태 로직을 건드리지 않고도 레이아웃을 바꿀 수 있고, UI를 다시 쓰지 않고 상태를 바꿀 수 있습니다. 전역 상태 대신 평범한 값과 콜백을 받는 위젯을 선호하세요.
리뷰 가능하게 유지하는 루프 예시:
- 슬라이스에 대한 3~6줄 UI 스펙 작성(무엇이 나타나고, 탭이 무엇을 하는지, 로딩/에러/빈 상태가 어떻게 보이는지).
- 필요한 최소 파일만 생성하거나 편집(대개 한 화면과 한두 위젯).
- 화면을 실행하고, 한 번의 정리 패스(네이밍, 간격, 사용되지 않는 props 제거).
- 슬라이스에 맞는 커밋 메시지로 커밋.
엄격한 규칙을 세우세요: 모든 변경은 되돌리기 쉽거나 격리 가능해야 합니다. 반복 중에는 끼어들기식 리팩터를 피하세요. 관련 없는 문제를 발견하면 적어두고 별도 커밋에서 고치세요.
툴이 스냅샷과 롤백을 지원하면 각 슬라이스를 스냅샷 포인트로 사용하세요. Koder.ai 같은 일부 비브 코딩 플랫폼은 스냅샷과 롤백을 포함해 실험을 더 안전하게 만듭니다.
초기 반복을 얌전하게 유지하는 또 하나의 습관: 공유 위젯을 편집하기보다 새 위젯을 추가하는 것을 선호하세요. 공유 컴포넌트는 작은 변경이 큰 Diff로 번지는 곳입니다.
단계별: 사용자 스토리에서 위젯 트리 생성하기
빠른 UI 작업은 사고와 타자를 분리할 때 안전합니다. 먼저 코드 생성 전에 명확한 위젯 트리 계획을 세우세요.
-
위젯 트리 개요만 요청하세요. 위젯 이름, 계층, 각 부분이 무엇을 보여주는지 원합니다. 아직 코드는 필요 없습니다. 이렇게 하면 상태 누락, 빈 화면, 이상한 레이아웃 선택 같은 것을 싸게 잡을 수 있습니다.
-
책임별 컴포넌트 분해를 요청하세요. 각 위젯을 집중시키세요: 하나는 헤더, 하나는 리스트, 하나는 빈/에러 UI를 렌더링합니다. 나중에 상태가 필요하면 지금 메모만 하고 아직 구현하지 마세요.
-
화면 스캐폴드와 stateless 위젯을 생성하세요. 플레이스홀더 컨텐츠와 명확한 TODO가 있는 단일 스크린 파일로 시작하세요. 입력은 명시적으로 유지(생성자 파라미터)하여 나중에 실제 상태를 쉽게 연결할 수 있게 합니다.
-
스타일과 레이아웃 세부사항은 별도 패스로 처리하세요: 간격, 타이포그래피, 테마, 반응형 동작. 스타일링을 별도의 Diff로 분리하면 리뷰가 단순해집니다.
효과적인 프롬프트 패턴
제약을 앞에 두어 어시스턴트가 실무에 맞지 않는 UI를 만들지 않게 하세요:
- 목표 기기(폰만, 태블릿 포함, 방향)
- 디자인 제약(Material 3, 기존 테마 색상, 간격 규칙)
- 네비게이션 기대(뒤로 동작, 딥 링크 여부)
- 수락 기준(무엇이 보여지고 탭 가능해야 하는가)
- 기존 코드 경계(어떤 파일/위젯은 그대로 있어야 하는지, 네이밍 규칙)
구체적 예시: 사용자 스토리가 "사용자는 저장된 항목을 검토하고 하나를 제거할 수 있다"라면 앱바, 항목 행이 있는 리스트, 빈 상태를 포함하는 위젯 트리를 요청하세요. 그런 다음 SavedItemsScreen, SavedItemTile, EmptySavedItems 같은 분해를 요청하세요. 그 다음에 stateless 위젯과 가짜 데이터를 가진 스캐폴드를 생성하고 마지막에 스타일(구분선, 패딩, 명확한 제거 버튼)을 추가하세요.
자주 묻는 질문
How do I keep a Flutter UI iteration small enough to review?
작고 테스트 가능한 UI 스펙을 먼저 만드세요. 3–6줄로 아래를 커버합니다:
- 무엇이 나타나는가 (핵심 위젯/컴포넌트)
- 탭하면 무슨 일이 일어나는가 (주요 상호작용 한 가지)
- 로딩/에러/빈 상태는 어떻게 보이는가
- 30초 내에 어떻게 검증할 수 있는가
그런 다음 그 슬라이스(대개 한 화면 + 1–2개의 위젯)만 만드세요.
What’s the best way to turn a user story into a buildable UI spec?
스토리를 네 가지 버킷으로 정리하세요:
- 스크린: 무엇이 바뀌고 무엇이 그대로인지
- 컴포넌트: 새 위젯과 위치
- 상태: 로딩, 빈, 에러, 성공(각 상태가 무엇을 보여주는지)
- 이벤트: 탭, 뒤로, 재시도, 새로고침, 폼 편집
수락 기준을 빠르게 설명할 수 없다면 스토리는 아직 UI로 바꾸기엔 불명확합니다.
What should I ask an AI assistant for first: code or structure?
먼저 위젯 트리 개요(이름 + 계층 + 각 부분이 무엇을 보여주는지)만 생성하도록 요청하세요. 코드 금지.
그다음 컴포넌트 책임 분해(각 위젯의 역할)를 요청하세요.
그다음에야 입력(값 + 콜백)을 명확히 한 상태로 stateless scaffold를 생성하고, 스타일링은 별도의 패스로 진행하세요.
Why does the “one big prompt” approach usually create messy diffs?
원칙: 한 의도당 한 번의 반복.
- 반복 A: 위젯 트리/레이아웃
- 반복 B: 상태 연결
- 반복 C: 네비게이션 연결
하나의 커밋에 레이아웃, 상태, 라우트를 모두 바꾸면 리뷰어가 무엇 때문에 문제가 생겼는지 알기 어렵고 롤백이 복잡해집니다.
How do I add state without bloating my widget code?
위젯은 덤하게 유지하세요: 상태를 렌더링만 하고 비즈니스 규칙을 결정하지 않게 합니다.
실용적 기본값:
- 이벤트와 비동기 작업을 소유하는 컨트롤러/뷰모델 한 개 만들기
- 하나의 상태 객체(loading/empty/error/success)를 UI에 노출
- UI는 상태를 읽고 콜백(retry, submit, toggle)을 호출
build() 안에 비동기 호출을 넣지 마세요—재빌드 시 반복 호출로 이어집니다.
Which UI states should I plan for on most screens?
코딩 전에 상태와 전이를 평범한 문장으로 적어 두세요.
일반 패턴 예시:
- Loading: 스피너 또는 스켈레톤 표시
- Empty: 메시지 + 액션(예: Retry)
- Error: 인라인 에러 + Retry
- Success: 콘텐츠 렌더링
그다음 어떤 이벤트가 상태를 바꾸는지(새로고침, 재시도, 제출, 편집)를 나열하세요. 코드가 문서와 비교하기 쉬워집니다.
How do I keep navigation flows from getting scattered and inconsistent?
스토리별로 작은 “흐름 지도”를 만드세요:\n\n- 진입: 사용자가 어디서 오는가\n- 다음: 주요 전진 경로\n- 취소: 중단하면 어디로 가는가\n- 뒤로: 뒤로가 무엇을 보존/버리는가\n- 대체경로: 필요한 데이터가 없으면 어떻게 처리할지
화면 간 전달되는 파라미터(ID, 필터, 임시 데이터)를 고정하세요. 그렇지 않으면 글로벌 싱글턴에 문맥을 숨기게 됩니다.
What folder structure helps keep UI changes contained?
기본은 기능별 폴더 구조로 변경 범위를 한정하는 것입니다. 예시:
lib/features/<feature>/screens/lib/features/<feature>/widgets/lib/features/<feature>/state/lib/features/<feature>/routes.dart
각 반복은 한 기능 폴더 내에서만 작업하고 주변 코드를 건드리지 마세요.
How do I make my Flutter UI more modular without over-engineering it?
인터페이스(입력/출력)를 안정화하고 내부는 자유롭게 바꾸세요.
- 공개 위젯 props는 작고 타입이 명확해야 합니다
- 값 + 콜백 전달을 선호하세요(글로벌 상태 직접 접근 금지)
- 라우트 인수는 명시적으로(보통 하나의 args 객체)
- 자주 바뀌지 않으면 위젯으로 추출하세요
리뷰어는 입력/출력이 안정적인지에 더 관심이 많습니다.
What’s a quick pre-commit checklist for a safe UI iteration?
2분 점검 리스트:
- 로딩/빈/에러/성공 상태를 트리거해보고 적절히 보이는가?
- 뒤로 버튼이 예상대로 동작하는가(이상한 점프 없음)?
- 변경 파일 수가 적고 책임이 분명한가?
- 나중에 문제 생기면 롤백 가능한가(임시 플래그/플레이스홀더 제거)?
가능하면(스냅샷/롤백 지원 시) 큰 레이아웃 변경 전 스냅샷을 찍어두세요.