| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- scss
- lifecycle
- JQ
- LLM
- 단축키
- 자바스크립트
- npm
- 배포
- java 콘솔 출력 차이
- develop
- Flutter
- abap
- UI/UX
- 앱심사
- JS
- 엡
- riverpod
- nodejs
- firebase
- unity
- 자바 포맷 출력
- java 출력
- java
- Clean Architecture
- react
- printf
- 자바 출력 방식
- ListView
- DART
- println
- Today
- Total
guricode
Flutter + Riverpod MVVM 기본 통신 예제 본문
아래 글은 Flutter 프로젝트에 Riverpod를 적용해 간단한 통신 시나리오(MVVM 구조)를 만드는 과정을 처음부터 끝까지 풀어쓴 기록이다. 각 단계마다 필요한 코드와 개념을 최대한 상세히 설명했으니, 한 번도 Riverpod을 써 본 적이 없어도 그대로 따라오면 동작하는 예제를 얻을 수 있다
1. Riverpod 패키지 추가
터미널에서 프로젝트 루트에 다음 명령을 실행한다.
flutter pub add flutter_riverpod
pubspec.yaml의 dependencies: 블록에 flutter_riverpod: ^버전이 자동으로 들어가고, flutter pub get까지 함께 수행된다.
2. 최상위 위젯을 ProviderScope로 감싸기
Riverpod은 내부적으로 전역 상태 저장소를 두는데, 이것을 ProviderScope 위젯이 관리한다.
runApp() 바로 아래에서 앱 전체를 한 번만 감싸 주면 된다.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'home_page.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
);
}
}
ProviderScope가 없으면 런타임에 No ProviderScope found 예외가 발생한다....처음에 이거 빼먹엇다가 헛고생만했다..
3. 화면을 그릴 위젯 구성
UI를 담당하는 HomePage에서는 두 가지 Riverpod API를 사용한다.
- ref.watch(provider) – 프로바이더가 내보내는 상태를 구독한다. 값이 바뀌면 위젯이 자동 리빌드된다.
- ref.read(provider) – 프로바이더를 한 번만 읽거나(재빌드 불필요) ViewModel 메서드를 호출할 때 쓴다.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'home_view_model.dart';
class HomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(homeViewModelProvider);
return Scaffold(
appBar: AppBar(title: const Text('MVVM + Riverpod 예제')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('name : ${state.user?.name ?? "(no data)"}'),
Text('age : ${state.user?.age ?? "(no data)"}'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
ref.read(homeViewModelProvider.notifier).getUser();
},
child: const Text('데이터 가져오기'),
),
],
),
),
);
}
}
4. 통신 결과를 담을 모델 클래스
예시 JSON은 { "name": "...", "age": 20 } 형태다. 이에 맞춰 User 모델을 만든다.
class User {
final String name;
final int age;
User({required this.name, required this.age});
factory User.fromJson(Map<String, dynamic> map) {
return User(
name: map['name'] as String,
age : map['age'] as int,
);
}
Map<String, dynamic> toJson() => {
'name': name,
'age' : age,
};
}
5. 모델을 가져올 Repository 클래스
실제 앱에서는 http 패키지로 API를 호출하겠지만, 여기서는 1 초 딜레이 후 더미 JSON을 반환한다.
import 'dart:convert';
import 'user.dart';
class UserRepository {
Future<User> getUser() async {
await Future.delayed(const Duration(seconds: 1));
const dummy = '''
{
"name": "이지원",
"age": 20
}
''';
final map = jsonDecode(dummy) as Map<String, dynamic>;
return User.fromJson(map);
}
}
6. 위젯이 관찰할 상태 클래스
화면에 보여 줄 데이터가 늘어날 때마다 이 클래스에 필드를 추가한다.
import 'user.dart';
class HomeState {
final User? user;
HomeState(this.user);
}
7. ViewModel 작성 – Notifier 상속
Notifier<State>를 상속받으면 state 필드를 통해 상태를 읽고, 새 객체를 할당하여 갱신할 수 있다.
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'home_state.dart';
import 'user_repository.dart';
class HomeViewModel extends Notifier<HomeState> {
@override
HomeState build() {
// 최초 상태: user가 없다
return HomeState(null);
}
Future<void> getUser() async {
final repo = UserRepository();
final user = await repo.getUser();
state = HomeState(user); // 상태 교체 → ref.watch가 붙은 위젯 리빌드
}
}
8. ViewModel을 공급하는 NotifierProvider
NotifierProvider<ViewModel, State>는 ViewModel 인스턴스를 생성·보관하고,
동시에 해당 ViewModel이 노출하는 state를 외부 Provider로 등록한다.
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'home_view_model.dart';
import 'home_state.dart';
final homeViewModelProvider =
NotifierProvider<HomeViewModel, HomeState>((ref) {
return HomeViewModel();
});
9. 위젯에서 상태 구독 및 함수 호출
- 상태가 변할 때마다 UI가 갱신되길 원할 때:
- final state = ref.watch(homeViewModelProvider);
- 한 번만 값을 읽거나 ViewModel 메서드를 쓰고 싶을 때:
- ref.read(homeViewModelProvider.notifier).getUser();
watch와 read의 차이를 항상 기억해 두면, 필요 이상으로 위젯이 재빌드되는 문제를 피할 수 있다.
마무리
- 패키지 설치 – flutter pub add flutter_riverpod
- ProviderScope – 앱 최상단에서 한 번 감싸기
- UI 위젯 – ConsumerWidget 또는 Consumer 사용
- Model – JSON <-> 객체 변환 로직 작성
- Repository – 통신 또는 로컬 DB 접근 담당
- State – UI가 관찰할 데이터 묶음
- ViewModel – Notifier로 상태 업데이트 로직 구현
- NotifierProvider – ViewModel+State를 전역으로 노출
- watch/read – 필요에 따라 상태 구독·단발성 호출 구분
이 구조를 바탕으로 실제 API 호출, 예외 처리, 로딩 스피너, 테스트 코드 등을 단계적으로 확장해 나가면 된다. 처음에는 파일이 조금 많아 보이지만, 역할이 뚜렷해 유지보수에 유리하다.
'앱 > Flutter&Dart' 카테고리의 다른 글
| Riverpod, MVVM 구조로 프로젝트를 설계하는 법 (2) | 2025.07.31 |
|---|---|
| TextEditingController가 뭐지? 왜 쓰는 걸까 (2) | 2025.07.30 |
| Dart에서 JSON 직렬화/역직렬화 완벽 정리 - toJson & fromJson 메서드 활용법 (1) | 2025.07.28 |
| late (0) | 2025.07.24 |
| Singleton Pattern(싱글톤 패턴) (0) | 2025.07.22 |