2025. 3. 7. 17:01ㆍ같이 공부합시다 - Flutter/Dart & Flutter 기초부터 실전까지
Flutter에서 Provider는 강력하지만, 전역 상태 공유 및 유지보수 측면에서 단점이 있습니다.
Riverpod은 Provider의 단점을 보완하고, 보다 강력한 상태 관리 기능을 제공합니다.
📃 개요
✅ StateProvider, StateNotifierProvider를 활용하여 상태를 관리하는 방법

🔔 주제
🔸 Riverpod이란? 기존 Provider와의 차이점
🔸 Riverpod 패키지 설치 및 프로젝트 설정
🔸 StateProvider를 활용한 간단한 상태 관리
🔸 StateNotifierProvider를 활용한 복잡한 상태 관리
🔸 ConsumerWidget과 ref.watch() 사용법
1️⃣ Riverpod 이란? 기존 Provider 와의 차이점
🔸 Riverpod 은 Provider 의 단점을 보완하여 보다 강력한 상태 관리 기능을 제공하는 라이브러리입니다.
🔸 Provider 와 달리 전역적으로 안전한 상태 공유가 가능하며, BuildContext 없이 상태를 관리할 수 있습니다.
📌 기존 Provider vs. Riverpod 비교
| 비교 항목 | Provider | Riverpod |
| 상태 변경 방식 | ChangeNotifier + notifyListeners() | StateProvider, StateNotifierProvider |
| 상태 접근 방식 | context.watch<T>(), context.read<T>() | ref.watch(), ref.read() |
| BuildContext 필요 여부 | 필요 | ❌ 불필요 |
| 상태 보존 | 앱 종료 시 사라짐 | 앱 종료 후에도 상태 유지 가능 |
| 전역 상태 공유 | 가능하지만 코드가 복잡해짐 | 더 쉬운 전역 상태 공유 가능 |
💡 Riverpod은 보다 유연하고 안정적인 상태 관리 기능을 제공하여 유지보수가 쉬운 코드를 작성할 수 있도록 돕습니다.
2️⃣ Riverpod 패키지 설치 및 프로젝트 설정
🔸 Riverpod을 사용하려면 패키지를 설치해야 합니다.
📌 터미널에서 아래 명령어 실행
flutter pub add flutter_riverpod
📌 설치 완료 후 pubspec.yaml에 아래 내용이 추가되었는지 확인
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.6.1 # 2025-03-07 기준 최신 버전
3️⃣ StateProvider를 사용한 간단한 상태 관리
🔸 StateProvider는 가장 기본적인 상태 관리 방식으로, 간단한 숫자 증가/감소 같은 기능을 구현할 때 사용됩니다.
📌 counter_provider.dart 파일 생성 후 아래 코드 추가
import 'package:flutter_riverpod/flutter_riverpod.dart';
// StateProvider를 사용하여 카운터 상태 관리
final counterProvider = StateProvider<int>((ref) => 0);
💡 StateProvider는 state 속성을 통해 값을 읽거나 변경할 수 있습니다. (screen 에서 state 로 접근할 수 있다는 의미)
4️⃣ Riverpod을 앱에 적용하기
🔸 Riverpod을 사용하려면 ProviderScope를 main.dart에 추가해야 합니다.
📌 main.dart에서 Riverpod을 등록
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'screens/counter_screen.dart';
void main() {
runApp(
const ProviderScope(child: MyApp())); // Riverpod 사용을 위한 ProviderScope 추가
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: "Riverpod Counter App",
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
),
),
home: const CounterScreen(),
);
}
}
🔔 코드 설명
🔸 ProviderScope를 runApp()에 감싸면 Riverpod을 사용할 수 있습니다.
🔸 MyApp 내부에서는 별도의 Provider 등록이 필요하지 않으며, ref.watch()로 직접 상태를 사용할 수 있습니다.
5️⃣ Counter 앱 구현 (Riverpod 활용)
🔸 Riverpod을 활용하여 ConsumerWidget을 사용한 Counter 앱을 만들어 봅니다.
📌 counter_screen.dart 에서 Riverpod 상태를 가져와 사용합니다.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/counter_provider.dart';
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider); // 상태 가져오기
return Scaffold(
appBar: AppBar(title: const Text("Riverpod Counter App")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("현재 카운트:", style: TextStyle(fontSize: 20)),
Text(
"$counter",
style: const TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => {ref.read(counterProvider.notifier).state--},
child: const Text("-1 감소"),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => {ref.read(counterProvider.notifier).state++},
child: const Text("+1 증가"),
),
],
),
],
),
),
);
}
}
🔔 코드 설명
✅ ref.watch(counterProvider) → counterProvider의 현재 값을 가져와 UI에 반영
✅ ref.read(counterProvider.notifier).state++ → 상태를 직접 수정하여 카운트 증가
✅ ConsumerWidget을 사용하여 WidgetRef ref를 통해 상태 접근 가능
📌 내용 요약
🔸 Riverpod은 기존 Provider보다 더 강력한 상태 관리 기능을 제공한다.
🔸 StateProvider를 사용하면 간단한 상태 값을 관리할 수 있다.
🔸 전역적으로 안전한 상태 공유가 가능하며, BuildContext 없이도 상태를 관리할 수 있다.
🔸 ConsumerWidget을 사용하면 UI에서 쉽게 상태를 읽고 업데이트할 수 있다.
🔸 ref.watch()와 ref.read()를 활용하여 상태를 UI에 반영할 수 있다.
6️⃣ 보너스
📌 기본 구성된 CounterScreen 을 Wigets 로 분리하는 방법
⚠️ 주의 ⚠️
🔸 분리할 때 중요한 점 → "이 위젯이 재사용될 가능성이 있는가?"
🔸 재사용 가능성 있음 ? 분리 : 적절한 수준에서 유지
🔥 기능별로 다음과 같이 분리하면 유지보수가 쉬워지고 코드가 정리됨.
- CounterDisplay → 카운트 값을 표시하는 부분
- CounterButtons → 증가/감소 버튼 UI
📌 프로젝트 구조
📂 counter_app_provider
├── main.dart (앱의 진입점)
├── providers/counter_provider.dart (Provider 상태 관리)
├── screens/counter_screen.dart (카운터 메인화면)
├── widgets/counter_display.dart (카운터 표시 위젯)
├── widgets/counter_buttons.dart (카운터 버튼 위젯)
✅ 1. widgets/counter_display.dart (카운트 값 표시)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/counter_provider.dart';
class CounterDisplay extends ConsumerWidget {
const CounterDisplay({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider); // ✅ 상태 감시
return Column(
children: [
const Text("현재 카운트:", style: TextStyle(fontSize: 20)),
Text(
"$counter",
style: const TextStyle(fontSize: 40, fontWeight: FontWeight.bold),
),
],
);
}
}
💡 이제 Counter 값을 표시하는 부분이 CounterDisplay 위젯으로 분리됨
✅ 2. widgets/counter_buttons.dart (증가/감소 버튼)
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/counter_provider.dart';
class CounterButtons extends ConsumerWidget {
const CounterButtons({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state--, // ✅ 감소 버튼
child: const Text("-1 감소"),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++, // ✅ 증가 버튼
child: const Text("+1 증가"),
),
],
);
}
}
💡 이제 버튼 UI도 CounterButtons 위젯으로 분리됨
✅ 3. screens/counter_screen.dart에서 위젯 활용
import 'package:flutter/material.dart';
import '../widgets/counter_display.dart';
import '../widgets/counter_buttons.dart';
class CounterScreen extends StatelessWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Riverpod Counter App")),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CounterDisplay(), // ✅ 카운트 값 표시
SizedBox(height: 20),
CounterButtons(), // ✅ 증가/감소 버튼
],
),
),
);
}
}
💡 이제 CounterScreen이 훨씬 더 깔끔해졌고, 재사용 가능한 위젯으로 구조화됨!
'같이 공부합시다 - Flutter > Dart & Flutter 기초부터 실전까지' 카테고리의 다른 글
| 📌 Day 16: Flutter HTTP 패키지를 활용한 API 요청 (GET, POST, Json 데이터 처리, FutureProvider< (1) | 2025.03.19 |
|---|---|
| 📌 Day 15: Riverpod을 활용한 간단한 CRUD 구현 (0) | 2025.03.17 |
| 📌 Day 13: Provider 상태 관리 적용 테스트 (Counter 앱, 다크모드까지) (1) | 2025.03.06 |
| 📌 Day 12: Provider 패턴 적용 (ChangeNotifier, Consumer) (0) | 2025.03.05 |
| 📌 Day 11: 상태 관리 개념 및 setState() 이해 (1) | 2025.03.04 |