📌 Day 14: Riverpod 상태 관리 패턴 (Provider와 비교하며 배우는 실전 적용법)

2025. 3. 7. 17:01같이 공부합시다 - Flutter/Dart & Flutter 기초부터 실전까지

728x90

Flutter에서 Provider는 강력하지만, 전역 상태 공유 및 유지보수 측면에서 단점이 있습니다.

Riverpod은 Provider의 단점을 보완하고, 보다 강력한 상태 관리 기능을 제공합니다.
 

📃 개요

StateProvider, StateNotifierProvider를 활용하여 상태를 관리하는 방법
 
 



 


🔔 주제


🔸 Riverpod이란? 기존 Provider와의 차이점
🔸 Riverpod 패키지 설치 및 프로젝트 설정
🔸 StateProvider를 활용한 간단한 상태 관리
🔸 StateNotifierProvider를 활용한 복잡한 상태 관리
🔸 ConsumerWidget과 ref.watch() 사용법
 
 
 
 
 


1️⃣ Riverpod 이란? 기존 Provider 와의 차이점

🔸 Riverpod 은 Provider 의 단점을 보완하여 보다 강력한 상태 관리 기능을 제공하는 라이브러리입니다.
🔸 Provider 와 달리 전역적으로 안전한 상태 공유가 가능하며, BuildContext 없이 상태를 관리할 수 있습니다.

📌 기존 Provider vs. Riverpod 비교

   
비교 항목ProviderRiverpod
상태 변경 방식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이 훨씬 더 깔끔해졌고, 재사용 가능한 위젯으로 구조화됨!

728x90