Skip to content

z8837/item_flow

Repository files navigation

item_flow

같은 기능의 앱을 여러 아키텍처 스택으로 구현해 비교 하는 Flutter 학습용 레포. Pixabay API 에서 이미지를 받아 로컬 DB/캐시에 저장하고 그리드로 보여주는 "갤러리" 앱 하나를, 상태 관리 / DI / 네트워크 / 로컬 DB 축을 바꿔 가며 s1 ~ s6 으로 구현

한 앱 안에 모든 스택이 공존 하고, --dart-define=STACK=sN 플래그로 실행 시 어느 스택으로 돌릴지 선택


앱 기능 (모든 스택 동일)

  • Pixabay API 로 꽃 이미지 리스트 fetch (flower, 30 건)
  • 로컬 DB 에 메타데이터 저장 → 오프라인에서도 조회 가능
  • 로컬 파일 캐시 (꽃 썸네일 + 작가 프로필 이미지)
  • 2 열 GridView + 당겨서 새로고침 + 오프라인 배너
  • 상세 화면 — 스크롤에 따라 이미지가 자연스럽게 축소되는 collapsing parallax hero, 태그/통계/AI 메타 플래그 표시, 사용자 이름 편집 + 저장
  • 선택 모드 → 여러 아이템 선택 → 확인 바텀시트 → 일괄 삭제
  • 사용하지 않게 된 user 는 자동 prune

스택 매트릭스

스택 State 관리 DI API 로컬 DB
s1 flutter_bloc self (수동 생성자 주입) dio (hand-rolled) realm
s2 flutter_bloc injectable + get_it dio (hand-rolled) realm
s3 flutter_bloc injectable + get_it retrofit + dio realm
s4 riverpod (generator) injectable + get_it retrofit + dio realm
s5 riverpod (generator) injectable + get_it retrofit + dio drift
s6 (계획) riverpod (generator) injectable + get_it retrofit + dio sqflite (raw)

dio 는 모든 스택 공통 HTTP client. s1s2 는 dio.get(...) 을 직접 호출하는 hand-rolled 버전, s3 부터는 retrofit 이 @GET 어노테이션으로 dio 호출 코드를 generator 가 만들어 주는 방식. retry interceptor / dio 설정 / DTO 는 전 스택 동일.

스택을 뒤로 갈수록 한 축씩만 교체 되므로, 이전 스택과 diff 가 학습 포인트가 됩니다 (s1→s2 는 DI, s2→s3 는 API, s3→s4 는 상태관리, s4→s5 는 DB).

스택별 특징 (한 줄 요약)

  • s1 — 모든 걸 수동 배선. 의존성 흐름이 한 파일 (self_dependencies.dart) 에 그대로 드러남.
  • s2 — s1 대비 DI 만 교체. @module / @LazySingleton 어노테이션 + injectable_generator
  • s3 — s2 대비 API 만 교체. @RestApi / @GET 어노테이션으로 retrofit_generator 가 HTTP 클라이언트 자동 생성.
  • s4 — s3 대비 상태관리만 교체. 단일 Bloc state → 4 개 작은 provider (StreamProvider / Notifier / AsyncNotifier) 로 분해, AsyncValue.guard 로 try/catch 보일러플레이트 제거.
  • s5 — s4 대비 로컬 DB 만 교체 (realm → drift/SQLite). FlowerRepository 를 추상 인터페이스로 추출해 realm/drift 두 구현을 env 로 스위칭.
  • s6 (계획) — s5 대비 ORM 제거. drift 가 자동으로 해 주던 것 (타입 안전 쿼리 / 스트림 재실행 / 마이그레이션) 을 raw sqflite 로 직접 풀어 봄.

폴더 구조

lib/
├── main.dart                               # STACK 분기 + MaterialApp 테마
│
├── core/                                   # 전 스택 공유 유틸/위젯
│   ├── colors.dart                         
│   ├── utils.dart
│   └── widgets/                            # message_view, offline_banner,
│                                           # refresh_error_banner, selection_app_bar,
│                                           # cached_image 등
│
├── shared/                                 # 스택 간 공유 도메인/데이터 계층
│   ├── domain/
│   │   ├── flower_repository.dart          # ★ 추상 인터페이스 (s1~s6 공통)
│   │   └── model/flower.dart               # freezed 도메인 모델
│   ├── data/
│   │   ├── api/                            # pixabay_api (abstract), flower_dto,
│   │   │                                   # retry_interceptor
│   │   ├── cache/image_cache_service.dart  # 파일 캐시
│   │   └── connectivity/                   # ConnectivityRepository
│   └── widgets/                            # flower_tile, flower_detail_page,
│                                           # delete_confirm_sheet 등
│
├── data_impl/                              # 구현 레이어 (스택별로 선택됨)
│   ├── retrofit/                           # PixabayApi 의 retrofit 구현 (s3~s6)
│   │   ├── pixabay_retrofit_api.dart       # PixabayApi 상속 + retrofit client 위임
│   │   └── pixabay_retrofit_client.dart    # @RestApi 인터페이스
│   │
│   ├── realm/                              # realm 기반 (s1~s4)
│   │   ├── entity/                         # @RealmModel (FlowerEntity, UserEntity)
│   │   ├── realm_flower_mappers.dart       # DTO ↔ Entity ↔ Domain
│   │   ├── realm_flower_local_data_source.dart
│   │   └── realm_flower_repository.dart    # implements FlowerRepository
│   │
│   └── drift/                              # drift 기반 (s5)
│       ├── tables/                         # Flowers, Users (Table 상속)
│       ├── dao/                            # FlowerDao, UserDao (@DriftAccessor)
│       ├── app_database.dart               # @DriftDatabase
│       ├── drift_flower_mappers.dart
│       ├── drift_flower_local_data_source.dart
│       └── drift_flower_repository.dart    # implements FlowerRepository
│
├── di/                                     # 의존성 주입
│   ├── self/self_dependencies.dart         # s1 전용 수동 배선
│   └── injectable/                         # s2~s6 공통
│       ├── injectable_dependencies.dart
│       ├── injectable_dependencies.config.dart  # ← codegen
│       └── modules/app_module.dart         # env 로 스택별 구현 선택
│
├── state_mgmt/                             # UI 상태 관리 계층
│   ├── bloc/                               # s1~s3 (Bloc)
│   │   ├── flower_list_bloc.dart
│   │   ├── flower_list_event.dart
│   │   ├── flower_list_state.dart
│   │   └── pages/flower_list_page.dart
│   └── riverpod/                           # s4~s6 (Riverpod)
│       ├── dependency_providers.dart       # getIt ↔ riverpod 브릿지
│       ├── flowers_provider.dart           # StreamProvider<List<Flower>>
│       ├── online_provider.dart            # Notifier<bool>
│       ├── selection_state.dart, selection_provider.dart
│       ├── flower_list_controller.dart     # AsyncNotifier<void> (액션)
│       └── pages/flower_list_page.dart
│
└── stacks/                                 # 스택별 진입점 (Bootstrap + Scaffold)
    ├── s1_bloc_self_realm/bootstrap.dart
    ├── s2_bloc_injectable_realm/bootstrap.dart
    ├── s3_bloc_injectable_retrofit_realm/bootstrap.dart
    ├── s4_riverpod_injectable_retrofit_realm/bootstrap.dart
    └── s5_riverpod_injectable_retrofit_drift/bootstrap.dart

핵심 원칙shared/core/ 는 전 스택 공통, data_impl/di/state_mgmt/stacks/ 는 스택별 선택. 공개 표면(도메인 인터페이스) 은 같고 구현만 env 로 스위칭되므로 같은 UI 가 어떤 스택을 고르더라도 그대로 동작.


요구 버전

버전
Flutter 3.41.7 (stable, 개발 환경 기준)
Dart SDK ^3.11.4 (pubspec)

주요 의존성 버전 pin 은 pubspec.yaml 참고. 특히 source_gen2.0.0 에 묶여 있어 drift_dev>=2.26.0 <2.28.0 로 pin 되어 있음 (2.28+ 는 source_gen ^3.0.0 요구 → 다른 generator 들과 비호환).


실행

# 의존성 설치
flutter pub get

# 코드 생성 (freezed / retrofit / riverpod / injectable / drift)
dart run build_runner build --delete-conflicting-outputs

# 스택 선택 실행
flutter run --dart-define=STACK=s1   # bloc + self + dio + realm
flutter run --dart-define=STACK=s2   # bloc + injectable + dio + realm
flutter run --dart-define=STACK=s3   # bloc + injectable + retrofit + realm
flutter run --dart-define=STACK=s4   # riverpod + injectable + retrofit + realm
flutter run --dart-define=STACK=s5   # riverpod + injectable + retrofit + drift

# 기본값은 s1
flutter run

--dart-define=STACK=sNconst _stack = String.fromEnvironment('STACK', defaultValue: 's1') 로 읽혀서 main.dartswitch 에서 해당 스택의 Bootstrap.build() 를 호출


환경변수

Pixabay API key 가 필요. 루트에 .env 파일을 만들고:

PIXABAY_API_KEY=your_pixabay_api_key_here

.env.example 참고.


License

MIT — LICENSE 파일 참고.

About

같은 Flutter 앱(API fetch + 로컬 DB 캐싱)을 여러 아키텍처 스택으로 구현해 비교하는 레포

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages