| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- JQ
- unity
- LLM
- 자바스크립트
- UI/UX
- scss
- 자바 출력 방식
- java
- lifecycle
- Clean Architecture
- 단축키
- riverpod
- 엡
- 앱심사
- react
- firebase
- 배포
- Flutter
- println
- JS
- abap
- printf
- java 출력
- nodejs
- DART
- java 콘솔 출력 차이
- npm
- 자바 포맷 출력
- develop
- ListView
- Today
- Total
guricode
Riverpod, MVVM 구조로 프로젝트를 설계하는 법 본문
시작 전에: MVVM + Riverpod?
Flutter는 자유도가 너무 높아서 그냥 짜면 View, 로직, 상태가 뒤섞이기 쉽다.
그래서 유지보수가 어려워지고, 테스트도 힘들고, 재사용도 안 된다.
그래서 역할을 나눠서(View-Model-ViewModel) 설계하면 깔끔해진다.
그리고 상태관리는 뭐 쓸까 고민하다가 — Riverpod이 구조 잡기에 최고더라.
다양한 Provider로 의존성 주입도 자연스럽고, 테스트도 쉽고, 전역 상태도 잘 관리됨.
1단계 - 모델(models): 데이터는 깨끗하게 보관하자
API 응답값을 맵 그대로 들고 다니면 에러 난다.
그래서 응답값을 Dart 객체로 바꿔서 쓰는 게 기본.
예를 들어 네이버 지역 검색 API에서 받은 응답은 이런 식:
{
"title": "OO치킨",
"roadAddress": "서울시 강남구 OO로 123",
"mapx": "126.123",
"mapy": "37.456"
}
이걸 Location이라는 모델 클래스로 만들어두면, 이후부터 타입 안정성 + 코드 자동완성까지 얻는다.
→ 딱 하나의 책임만 있는 클래스: 데이터 표현
2단계 - 서비스(services): 외부와 통신하는 창구
여기서 중요한 건 "외부"라는 키워드.
HTTP 요청이나 Firebase 같은 외부 시스템은 언제든지 바뀔 수 있다.
그래서 앱 내부랑 직접 엮지 말고, service 단에서만 통신하게 제한하는 게 맞다.
네이버 API는
NaverApiServiceFirestore는
FirestoreService
이런 식으로 외부 API마다 파일 하나씩 만들어서 의존성과 변동성을 이 레이어에 가둬놓는다.
3단계 - 레포지토리(repositories): 서비스 가공하고 뷰모델에게 전달
서비스는 잘 만들었지만, 실제 앱에서 쓸 때는 전처리가 필요하다.
예외 처리도 해야 하고, 특정 조건 처리, 데이터 정렬 같은 것도 해야 함.
그래서 repository를 하나 더 둔다.
목표는 ViewModel이 정말 간단한 인터페이스만 갖도록 도와주는 것.
예:
final results = await locationRepository.search("강남 치킨");
이런 한 줄이면 결과가 잘 오도록 내부 로직을 repository가 다 감싸주는 느낌.
4단계 - 뷰모델(viewmodels): 상태(State)와 로직(Action)을 가진 클래스
이제 진짜 핵심이다.
상태를 직접 들고 있고, 사용자의 입력에 따라 상태를 업데이트하는 로직이 여기에 들어간다.
그리고 여기에 Riverpod의 StateNotifier 또는 AsyncNotifier를 쓴다.
그럼 상태 변화를 감지해서 View에 자동으로 알려줄 수 있다.
final locationVMProvider = StateNotifierProvider<LocationVM, AsyncValue<List<Location>>>(...);
ViewModel의 핵심은 'View와 데이터를 연결하는 다리 역할'이다.
View가 직접 로직을 갖지 않도록 책임을 이곳으로 넘기는 게 포인트.
5단계 - 뷰(views): UI만 만들자. 오직 보여주기만.
여기서는 ref.watch()로 상태를 구독하고,ref.read(...notifier).search()처럼 뷰모델에 명령만 전달한다.
그러면 UI 코드가 정말 깔끔해진다.
로딩 상태면 로딩 표시, 데이터 상태면 리스트 출력, 에러 상태면 에러 메시지.
→ View는 “지시받은 대로 보여주는 역할”만 한다.
정리하며 느낀 점
처음에는 폴더 나누고 Provider 나누는 게 복잡하게 느껴졌다.
그런데 한 번 익숙해지니까, 실제 기능 추가하거나 수정할 때 훨씬 빠르고 안전하다.
구조가 잘 잡혀있으면 버그도 덜 나고, 코드도 보기 좋고, 동료랑 협업도 수월하다.
마지막 한 줄 요약
MVVM 구조로 나누는 이유는 "View, Logic, State를 명확히 분리"해서 유지보수성과 확장성을 높이기 위함이고, Riverpod은 그걸 자연스럽게 가능하게 해주는 도구다.
'앱 > Flutter&Dart' 카테고리의 다른 글
| BottomNavigationBar_Widget (1) | 2025.08.04 |
|---|---|
| Consumer_Riverpod (0) | 2025.08.01 |
| TextEditingController가 뭐지? 왜 쓰는 걸까 (2) | 2025.07.30 |
| Flutter + Riverpod MVVM 기본 통신 예제 (2) | 2025.07.29 |
| Dart에서 JSON 직렬화/역직렬화 완벽 정리 - toJson & fromJson 메서드 활용법 (1) | 2025.07.28 |