MVVM 구조 복습
- Model: 데이터를 정의하는 구조체.
→ JSON, API, DB 등 외부 데이터의 형태를 정의하고, 변환 로직(fromJson, toJson)을 포함. - Repository: 데이터를 “어디서 가져올지” 관리하는 역할.
→ 예: Firestore, REST API, Local DB 등과 통신하고, 받은 데이터를 Model로 변환. - ViewModel: Repository로부터 데이터를 받아 state를 변경하고, View(UI)에 반영되도록 함.
- View(UI): ref.watch()를 통해 상태 변화를 감지하고 자동으로 다시 빌드.
요약
Model은 “데이터 구조”, Repository는 “데이터 출처”, ViewModel은 “상태 관리”, View는 “표현 담당”.
state.count.count의 의미
- state → HomeState 객체
- state.count → Count 클래스 객체
- state.count.count → 실제 숫자 값 (int)
즉, 이중 구조 때문에 이렇게 접근하게 되는 것.
개선 방법: Count 클래스의 필드명을 value로 변경
→ state.count.value 로 간결하고 직관적으로 표현 가능.
Future의 필요성
- 현재 incrementCounter()는 단순히 숫자 더하기이므로 비동기(Future, async/await) 불필요.
- Future는 Repository처럼 서버 통신(DB, API 등)에서만 사용해야 함.
수정 전
Future<void> incrementCounter() async { final counter = await state.count.count; state = HomeState(Count(counter + 1)); }
수정 후
void incrementCounter() { final counter = state.count.value; state = HomeState(Count(counter + 1)); }
ConsumerWidget vs ConsumerStatefulWidget
구분설명사용 시점
| ConsumerWidget | Riverpod 상태만 구독. ref.watch 가능 | 단순 상태 UI |
| ConsumerStatefulWidget | Riverpod + initState, dispose 같이 사용 가능 | TextController, Animation 등 필요할 때 |
| StatefulWidget | setState()로 직접 UI 갱신 | Riverpod 안 쓸 때만 |
결론
Riverpod에서는 ConsumerWidget이 기본,
UI 내부 컨트롤러가 필요할 때만 ConsumerStatefulWidget 사용.
NotifierProvider 선언 방식
- Riverpod 3.0.3 버전에서는 다음과 같이 선언해야 함 👇
final homeViewModelProvider = NotifierProvider<HomeViewModel, HomeState>(HomeViewModel.new);
- .new 생성자 참조 방식을 써야 최신 문법과 호환됨.
- 변수명은 lowerCamelCase (homeViewModelProvider) 형식으로 선언.
StatefulWidget이 불필요한 이유
- Riverpod은 상태 변화를 자동 감지하여 rebuild하기 때문에
→ setState()나 StatefulWidget이 필요 없음. - 대신 ViewModel(Notifier)에서 상태를 바꾸고, View는 ref.watch()로 반응.
오늘 배운 핵심 3줄 정리
- MVVM 구조 핵심: Model(데이터) → Repository(출처) → ViewModel(상태) → View(UI).
- Future는 비동기 로직일 때만, 단순 로직엔 불필요.
- Riverpod은 setState()를 대체하며, 대부분의 화면은 ConsumerWidget으로 충분하다.