Skip to content

Flutter 폴더 / 패키지 구조

Flutter 앱(mobile/)의 폴더 구조와 각 레이어 책임. 시스템 전체 흐름은 architecture.md, 도메인별 동작은 domain-*.md 참조.

디렉토리 트리

mobile/
├── lib/
│   ├── main_dev.dart           # dev 진입점 — bootstrap(AppFlavor.dev)
│   ├── main_prod.dart          # prod 진입점 — bootstrap(AppFlavor.prod)
│   ├── firebase_options.dart   # Firebase config (dev / prod 분기)
│   │
│   ├── app/                    # 앱 셸 — 진입 / 라우팅 / Flavor
│   │   ├── app.dart            # DartBriefApp (MaterialApp.router)
│   │   ├── bootstrap.dart      # Kakao + Firebase + App Check + AdMob 초기화
│   │   ├── app_flavor.dart     # AppFlavor enum (dev / prod)
│   │   ├── flavor_provider.dart # appFlavorProvider (Riverpod)
│   │   ├── router.dart         # GoRouter + StatefulShellRoute
│   │   └── nav_bar_tuning.dart # 하단 floating pill 네비
│   │
│   ├── features/               # 도메인 모듈 (presentation / application / data)
│   │   ├── auth/
│   │   ├── disclosure/
│   │   ├── notifications/
│   │   ├── quota/
│   │   ├── settings/
│   │   └── watchlist/
│   │
│   └── shared/                 # 피처 공통 인프라
│       ├── api/                # dio 클라이언트 + 토큰 저장
│       ├── auth/               # 전역 AuthController + UserInfo
│       ├── browser/            # 인앱 브라우저 시트
│       ├── debug/              # 인앱 로그 패널 (DebugLog)
│       ├── format/             # 날짜 포맷 유틸
│       ├── splash/             # 스플래시 애니메이션
│       ├── theme/              # 디자인 토큰 (color / typography / spacing / radius)
│       └── toast/              # 토스트 서비스

├── android/                    # 네이티브 — flavor 설정, app/build.gradle, AdMob/App Check 메타
├── ios/                        # 네이티브 — Runner.xcodeproj, Info.plist (flavor별)
├── pubspec.yaml                # 의존성
└── Makefile                    # `make run-dev` / `run-prod` (Tailscale IP 자동 주입)

레이어 패턴

features/{X}/는 동일한 3-folder 구조:

features/{X}/
├── application/    # Riverpod 컨트롤러 (Notifier / AsyncNotifier)
├── data/           # Repository + Model
└── presentation/
    ├── pages/      # 라우트로 매핑되는 화면
    └── widgets/    # 그 화면에서만 쓰는 위젯
  • presentation → application (Provider 의존)
  • application → data (Repository 의존)
  • data → shared/api/api_client.dart (HTTP)
  • presentation → shared/theme (디자인 토큰)

reverse dependency는 금지 (data가 presentation을 알지 못함).

진입 / 부트스트랩

main_dev.dart / main_prod.dart

dart
// main_prod.dart
void main() => bootstrap(AppFlavor.prod);

진입점은 단 한 줄. flavor만 다름.

app/bootstrap.dart

bootstrap(flavor) 순서:

  1. WidgetsFlutterBinding.ensureInitialized()
  2. KakaoSdk.init(nativeAppKey: ...)KAKAO_NATIVE_APP_KEY env
  3. Firebase.initializeApp(options: firebaseOptionsFor(flavor))
  4. FirebaseAppCheck.activate(...) — flavor에 따라 Play Integrity / AppAttest / Debug
  5. MobileAds.instance.initialize() (fire-and-forget)
  6. runApp(ProviderScope(overrides: [appFlavorProvider.overrideWithValue(flavor)], child: DartBriefApp()))

dev flavor에서만 DebugLog 버퍼를 ProviderContainer에 attach.

app/router.dart

GoRouter 정의:

경로화면비고
/loginLoginPage비로그인 시 redirect target
/disclosures/:rceptNoDisclosureDetailPage디테일 / lazy reveal
/disclosuresDashboardPage (관심 + 최근)StatefulShellRoute 탭 1
/notificationsNotificationsPage탭 2
/settingsSettingsPage탭 3

redirect 룰:

  • authControllerProvider.user == null/login
  • 로그인된 채 /login 진입 → /disclosures
  • _AuthRefreshListenableauthController 변화 감지해 router 재평가

상태 관리: Riverpod

전역

Provider위치역할
authControllerProvidershared/auth/auth_controller.dart콜드 스타트 시 /auth/me 자동 fetch. 로그인/로그아웃/탈퇴 액션. user 상태 변화는 router redirect를 트리거
appFlavorProviderapp/flavor_provider.dartbootstrap에서 override

피처별

Provider위치역할
disclosureDetailControllerProvider.familyfeatures/disclosure/application/disclosure_detail_controller.dartrceptNo별 디테일 + 분석 메타 + reveal 상태
rewardedAdControllerProviderfeatures/disclosure/application/rewarded_ad_controller.dartRewardedAd preload / show / 보상 처리
feedbackControllerProviderfeatures/disclosure/application/feedback_controller.dart분석 피드백 (👍/👎)
quotaControllerProviderfeatures/quota/application/quota_controller.dartquota 상태 (auth.user?.userId watch — stale 요청 방지)
watchlistControllerProviderfeatures/watchlist/application/watchlist_controller.dart관심종목 (optimistic toggle)
dashboardDisclosuresControllerProviderfeatures/watchlist/application/dashboard_disclosures_controller.dart관심종목 기준 공시 목록
recentDisclosuresControllerProviderfeatures/watchlist/application/recent_disclosures_controller.dart전체 최근 공시
notificationsControllerProviderfeatures/notifications/application/notifications_controller.dart알림 권한 상태 (FCM 토큰 등록은 미구현)

규칙: Provider는 application/ 안에만, presentation 안에는 두지 않는다. presentation은 ref.watch(...)로 소비만.

데이터 레이어

Repository

파일역할
features/disclosure/data/disclosure_repository.dartGET /disclosures/{rceptNo}, GET /ai/analyze/{rceptNo} (메타), POST /ai/analyze/{rceptNo}/reveal
features/quota/data/quota_repository.dartGET /quota, POST /quota/ad-reward (legacy dev 전용)
features/watchlist/data/watchlist_repository.dartGET/POST/DELETE /watchlist, GET /watchlist/capacity

Model

*_models.dart는 freezed/json_serializable 기반 sealed/data class. 상태 변형이 있는 응답은 sealed (AnalysisMetaState, RevealResult) 으로 분기.

Shared 인프라

shared/api/

파일역할
api_client.dartDio 인스턴스 + 인터셉터 (JWT, App Check 헤더, 401 → 자동 logout). generic request<T> + requestRaw 두 가지
api_base_url.dartflavor별 baseUrl (https://api.dartbrief.com vs Tailscale IP)
api_error.dartApiError (status + code + message)
token_storage.dartFlutterSecureStorage 래퍼. key=dartbrief_auth_token

shared/auth/

파일역할
auth_controller.dartAuthController extends Notifier<AuthState>. 로그인 / 로그아웃 / 탈퇴 트리거
user_info.dartUserInfo 모델

shared/theme/

파일역할
app_theme.dartAppTheme.light() (Material 3, CJK fallback fonts)
app_colors.dart, app_typography.dart, app_spacing.dart, app_radius.dart토큰. ThemeData.extensions로 주입 → Theme.of(context).extension<AppColors>()

shared/debug/

dev flavor에서만 활성. bootstrap이 ProviderContainer에 DebugLog 인스턴스 attach. 디테일 페이지에서 길게 누르면 DebugPanel이 떠 인앱에서 로그 확인. 모바일 디버깅 시 추측 없이 로그 우선 보기 위함 (memory: feedback_dont_guess_use_logs).

shared/toast/, shared/splash/, shared/format/, shared/browser/

각각 토스트 / 스플래시 애니메이션 / 날짜 포맷 / 인앱 브라우저 시트.

Build Flavor

항목devprod
진입점main_dev.dartmain_prod.dart
API base URLTailscale IP (Makefile이 자동 주입)https://api.dartbrief.com
Firebasedev projectprod project
App CheckDebug providerPlay Integrity (Android) / AppAttest (iOS)
AdMob 광고 IDGoogle 테스트 ID실 unit ID
광고 보상 경로POST /quota/ad-reward (legacy)AdMob SSV 콜백만
DebugLog활성비활성

상세는 docs/dev/environment.md, 정책은 docs/policies/monetization.md.

컨벤션

  • 위젯 라이브러리: phosphor_flutter만. Material Icons / Lucide 금지 (CLAUDE.md)
  • dialog: 네이티브 AlertDialog 금지. showModalBottomSheet / showDialog 커스텀
  • 보조 인터랙션: 인라인. 피드백 / 평가는 별도 큰 카드로 분리하지 않음
  • strict mode: analysis_options.yamlstrict-casts: true 등. dynamic / 무분별한 as 금지
  • 이미지/asset: pubspec.yaml에 명시
  • 새 패키지 추가flutter pub addpubspec.lock 커밋. iOS는 pod install 필요할 수 있음

새 화면 추가 체크리스트

  1. features/<X>/ 폴더 생성 (presentation / application / data)
  2. data/ — repository + model
  3. application/ — controller (Notifier / AsyncNotifier) + provider
  4. presentation/pages/<X>_page.dart
  5. presentation/widgets/ — 화면 전용 위젯
  6. app/router.dart에 라우트 등록
  7. 디자인 토큰은 shared/theme/에서만 가져오기 (직접 Color(0xFF...) 금지)
  8. PR 전 docs 업데이트: