비개발자가 Claude Code로 Flutter 앱을 만든다는 것 — GentleDo 개발기
Flutter를 한 줄도 안 써 본 비개발자가 Claude Code와 함께 GentleDo를 App Store에 올리기까지. 기술 스택 선택, DB 마이그레이션, 의존성 정리, 테스트 158개. AI가 할 수 있는 것과 할 수 없는 것의 경계를 정직하게 기록.
시리즈 위치 — #10 GentleDo라는 이름이 생기기까지 — Keelry를 버린 날 → #11 (현재) → #12 App Store 심사 2일 통과기
Flutter를 한 줄도 써 본 적 없던 내가, 2026년 4월 App Store에 GentleDo를 올렸다. 모든 코드는 Claude Code와 함께 썼다. 바이브 코딩이라고 부른다.
기술 스택 — 왜 Flutter + Drift + Riverpod인가
결정은 세 가지 제약에서 출발했다. (1) 혼자 만든다 (2) 서버 운영 비용은 0원에 수렴해야 한다 (3) iOS와 Android를 한 번에 올리고 싶다.
| 레이어 | 선택 | 이유 |
|---|---|---|
| Framework | Flutter 3.x | 크로스플랫폼 1개 코드베이스. SwiftUI × Jetpack Compose 병렬 학습은 1인에게 불가능 |
| Language | Dart 3.x (null-safe) | 정적 타입 + null 안정성 — AI 페어 프로그래밍과 잘 맞는다 |
| Local DB | Drift 2.x (SQLite ORM) | 서버 없음. Phase 1은 전부 기기 로컬 |
| State | Riverpod 2.x | Provider보다 컴파일 타임 안전성 높음. 테스트 가능성 우위 |
| Navigation | GoRouter | 선언형 라우팅. URL 스킴 확장 용이 |
| Notifications | flutter_local_notifications + flutter_timezone | 서버 푸시 불필요. 타임존 버그가 많았다 |
| Widget | home_widget | iOS WidgetKit + Android AppWidget 공통 API |
가장 중요한 결정은 Phase 1에서 서버를 절대 넣지 않기였다. Supabase든 Firebase든, 로컬 가설("에너지 체크인 한 날이 태스크 완료율이 높다")을 검증하기 전에 인프라를 쌓는 건 완벽한 시스템 함정이라는 걸 이전 프로젝트에서 배웠다.
바이브 코딩의 현실 — 내가 한 일, AI가 한 일
Claude Code가 혼자 다 해주는 게 아니다. 내가 한 일과 AI가 한 일의 경계는 이렇게 나뉘었다.
내가 한 일
- 프로젝트 컨텍스트 문서화 (
CLAUDE.md,docs/PROJECT_BRIEF.md,docs/CONVENTIONS.md) - 한 커밋당 3~5개 파일만 바꾸는 규칙 강제
- 실기기 테스트, 디자인 결정, 카피라이팅
- "이건 지금 하지 마세요" 리스트 유지 (Supabase, AI API, Material Icons 등)
- 가설 정의와 검증 지표 설정
Claude Code가 한 일
- Dart 코드 실제 작성
- Drift 스키마 마이그레이션 코드
- Riverpod provider 설계
- 반복 태스크 스케줄링 로직
- 테스트 케이스 작성
- 리팩토링 제안
핵심은 CLAUDE.md였다. 규칙을 명문화하지 않으면 AI는 매번 조금씩 다른 스타일로 짠다. "Red 사용 금지", "UI 문자열 하드코딩 금지(ARB 경유)", "Material Icons 금지(lucide_icons만)", "스트릭 카운터 절대 금지". 이 네 줄이 수백 번의 "그거 말고" 소통을 절약했다.
DB 마이그레이션 — schemaVersion 3까지의 흔적
로컬 DB를 쓰면 마이그레이션에서 반드시 한 번은 피를 본다. GentleDo는 schemaVersion = 3까지 왔다.
| 버전 | 변경 | 이유 |
|---|---|---|
| v1 | 초기 스키마 | 태스크 + 카테고리 + 컨디션 체크 |
| v2 | momentumScore 리네임 (구 keelryScore) | 브랜드 변경(#10 참고) |
| v3 | start_time, notify_enabled, notify_at_start, notify_minutes_before 추가 | 시작일/마감일 분리 + 알림 오프셋 |
실기기에서 v2 → v3 마이그레이션이 TestFlight 빌드에서 한 번 실패했다. ALTER TABLE 순서가 dev 기기에선 통과했는데 일부 테스터 기기에선 컬럼 기본값이 NULL로 들어가면서 크래시. Claude Code에게 "기본값을 명시적으로 넣어달라"고 두 번 지시한 뒤에야 안정화됐다.
교훈: 마이그레이션은 dev 기기 1대가 아니라 최소 3대 기기에서 검증해야 한다.
의존성 방향 정리 — 4월 중순의 대규모 리팩토링
초반에는 features/today에서 features/tasks를 직접 import했다. 편리했지만 탭이 4개가 되니 서로가 서로를 import하기 시작했다. 순환 참조는 없었지만 어디서 뭐가 쓰이는지 더 이상 머리에 그려지지 않았다.
결국 단방향 의존성 규칙을 세웠다.
app → features → shared → data → core
- features 간 직접 import는 today(허브)만 허용
- shared는 features를 import하지 않는다
- data는 core만 import한다
이 리팩토링에 1주일을 썼다. commit 473bfbc와 18ee6aa로 마무리. 결과적으로 App Store 심사 전 대형 파일이 쪼개졌고, 새 기능 추가가 훨씬 빨라졌다.
테스트 75 → 158개로 늘린 이유
초반엔 테스트 75개였다. 출시 1주 전, 158개로 두 배 넘게 늘렸다. 이유는 단순하다.
실기기에서 예상 못 한 크래시가 계속 터졌다.
- 반복 태스크 복구(Android 재부팅 후 알림 재예약) 경로
- 서브태스크 체크 시 부모 카드 상태 동기화
- 컨디션 체크 스킵했을 때 에너지 필터 fallback
- 다크모드 + 낮은 명도 컬러 조합의 대비
QC agent를 별도로 돌려서 리포지토리, 프로바이더, 헬퍼 커버리지를 끌어올렸다(commit 263610a). 테스트가 2배 늘자 심사 제출 빌드(build 8)에서 처음으로 내가 두려워하지 않고 "업로드" 버튼을 눌렀다.
한계 — AI가 못하는 것
정직하게 쓴다. 바이브 코딩으로 풀 수 없었던 것들이다.
- 실기기 디버깅 — path_provider_foundation 2.6.0의 FFI 크래시는 심사 제출 직전에 발견됐다. Claude에게 "이 크래시 로그 해결해줘"라고 던져도 원인을 특정하지 못했다.
dependency_overrides로 2.4.4 고정이라는 해결책은 결국 내가 패키지 이슈 트래커를 뒤져서 찾았다(#12에서 상세히) - Xcode 설정의 체크박스 하나 — TestFlight 암호화 선언, iPad 타깃 제거, capabilities, provisioning profile. 여기서 하루를 잃었다
- 디자인 감각 — "이 카드가 좀 무거워 보인다" 같은 피드백은 텍스트로 표현하기 어렵다. 결국 내가 Figma에서 직접 조정한 뒤 값을 옮겼다
- 사용자 공감 — 테스터의 "이건 왜 이렇게 되어 있어요?" 질문에 답하는 건 사람의 일이다
다음 이야기
다음 글 #12에서는 App Store 심사 2주간의 실전 기록을 쓴다. path_provider 크래시를 어떻게 잡았는지, iPad 스크린샷 요구를 어떻게 회피했는지, 그리고 제출 후 2일 만에 승인된 순간까지.
GentleDo가 궁금하면 지금 받아볼 수 있다 — App Store (iOS).
CTA — 다운로드 (iOS): apps.apple.com/us/app/gentledo/id6761796867 · GentleLab 랜딩: lsy860224.github.io/gentlelab.github.io
관련 글
App Store 심사 2일 통과기 — path_provider 크래시와 iPad 함정
2026-04-20 제출, 2026-04-22 승인. iOS 1.0.0 심사는 2일 만에 끝났지만 그 전 2주가 전쟁이었다. path_provider FFI 크래시, iPad 스크린샷 함정, build 8까지의 삽질 실전기.
GentleDo라는 이름이 생기기까지 — Keelry를 버린 날
3개월 키운 앱 이름 "Keelry"를 버리고 "GentleDo"로 바꾼 과정. 항해 메타포에 빠졌다가 돌아온 이야기, DB 컬럼 리네임, 그리고 GentleLab이라는 우산 브랜드가 생긴 날의 기록.
네이버 블로그도 시작했다 — 이중 플랫폼으로 간 이유
aigrit.dev만으로 충분할 줄 알았다. 그런데 한국 블로그 수익 구조를 파보니 광고·협찬·공구가 다른 채널에 있었다. 왜 네이버를 추가했고 어떻게 '복사'가 아닌 '에디션'으로 갔는지 기록.