- 프로젝트 기간: 2026.05
- 인원: 개인 프로젝트
- 기술스택: Flutter · Dart · go_router · Provider · SharedPreferences · http
iOS로 구현했던 Dogether의 주요 앱 플로우를 Flutter로 재구현하며 선언형 UI, Widget Tree, 상태 구독, 라우팅, 데이터 계층 분리를 학습한 프로젝트
flowchart LR
Screen["Screen / Widget"] --> ViewModel["ViewModel<br/>ChangeNotifier"]
Screen --> UseCase["UseCase"]
ViewModel --> UseCase
UseCase --> Repository["Repository Interface"]
Repository --> Data["API / Mock Repository"]
Data --> Core["ApiClient / AppStorage / AppError"]
-
Screen · ViewModel · UseCase · Repository 계층 분리로 화면 복잡도 관리
- Screen은 UI 조립과 이벤트 연결에 집중
- 화면 상태는 ViewModel에서 관리
- 기능 흐름은 UseCase에서 조합
- 데이터 접근은 Repository로 분리
-
Provider와 ChangeNotifier 기반 화면 상태 관리
- 선택 그룹, 날짜 오프셋, 필터 등 화면 상태를 ViewModel에서 관리
- 인증 draft, 리뷰 토스트처럼 화면 간 유지가 필요한 상태를 별도 ViewModel로 분리
notifyListeners를 통해 상태 변경과 Widget rebuild를 연결
-
go_router 기반 화면 전환 흐름 정리
- AppRoute로 화면 경로를 명시
context.goRoute/context.pushRoute로 이동 방식을 구분- route-local Provider로 특정 화면에서만 필요한 상태 수명을 관리
-
Repository Interface와 API/Mock 구현체 분리
- abstract class로 Repository 계약을 정의
- API 구현체와 Mock 구현체가 같은 계약을 따르도록 구성
- UseCase가 실제 데이터 출처에 의존하지 않도록 분리
-
공통 ApiClient · AppStorage · AppError로 외부 의존성 표준화
- 네트워크 요청은 ApiClient로 공통화
- 로컬 저장소 접근은 AppStorage로 캡슐화
- 서버 에러 변환은 AppError로 표준화
- 기존 iOS 구조
- ViewController가 ViewModel 상태를 구독
- 상태 변경 시 UIKit view 속성을 직접 갱신
- Flutter에서의 접근
- 상태 변경이
build()재실행으로 연결 - Screen은 현재 상태에 맞는 Widget Tree를 조립
- 상태 변경이
- 전환하며 느낀 차이
- 화면을 직접 수정하기보다 UI를 다시 설명하는 사고가 중요했음
- Widget 분리 기준과 rebuild 범위를 함께 고려하게 됨
- 기존 iOS 구조
- ViewModel이 Relay/Observable로 상태 전달
- ViewController가 상태 스트림을 구독해 화면 갱신
- Flutter에서의 접근
context.watch로 rebuild가 필요한 상태를 구독context.read로 이벤트 순간에 필요한 의존성만 호출
- 전환하며 느낀 차이
- 같은 Provider라도 구독 대상과 단순 호출 대상을 구분해야 했음
notifyListeners호출 위치가 화면 갱신 흐름을 이해하는 기준이 됨
- 기존 iOS 구조
viewDidLoad,viewWillAppear,deinit기준으로 생명주기 처리- 초기화, 재진입, 해제 작업을 ViewController에서 관리
- Flutter에서의 접근
initState에서 최초 초기화didChangeDependencies,build,dispose로 의존성 변경, UI 생성, 해제를 분리
- 전환하며 느낀 차이
build()는 초기화 지점이 아니라 여러 번 실행될 수 있는 UI 선언 함수였음- controller 생성과 해제 위치를 명확히 분리해야 했음
- 기존 iOS 구조
- Coordinator가 ViewController 생성과 화면 전환 정책을 관리
- push/pop, root 교체, modal 흐름을 코드에서 중앙 제어
- Flutter에서의 접근
AppRoute와app_router로 화면 경로를 명시- route-local Provider로 상태 수명을 화면 수명에 맞춤
- 전환하며 느낀 차이
- 화면 인스턴스보다 route path와 typed argument가 이동의 기준이 됨
- 화면 전환과 상태 생성 위치를 함께 설계해야 했음
- 기존 iOS 구조
- Decodable DTO로 서버 응답 수신
- Repository 또는 UseCase에서 Entity/ViewData로 변환
- Flutter에서의 접근
- Repository 구현체와 mapper에서 Dart entity로 변환
- Screen이 API 응답 원본에 의존하지 않도록 구성
- 전환하며 느낀 차이
- Swift의 DTO 분리 기준은 Flutter에서도 유효했음
- Dart의 abstract class와 sealed class로 계약과 성공/실패 분기를 표현할 수 있었음