본문 바로가기
앱개발/flutter

[Flutter] flutter 상태 관리 패키지 : Riverpod(Provier, Notifier, ref)

by 씐 2025. 3. 23.
728x90

Riverpod

 

Riverpod이란?

  • flutter의 강력한 상태 관리 라이브러리
  • Provider의 단점을 개선
  • MVVM 아키텍처에서 ViewModel을 쉽게 구현할 수 있고, View에서 ViewModel의 관찰을 쉽게 하게 해줌

 

Riverpod의 주요 특징

  • 타입 안전성 및 컴파일 타임 오류 감지
  • 반응형 상태 관리 : 데이터 변경 시 UI 자동 업데이트 및 불필요한 리빌드 방지
  • 의존성 주입 : Provider를 통해 여러 곳에서 동일한 인스턴스 공유 가능
  • 데이터 캐싱 및 API 관리 : 원격 데이터 소스를 효율적으로 가져옴
  • 전역 접근 가능 : context 없이도 어디서든 상태에 접근 가능
  • 테스트 친화적

Provider와 Notifier

 

Provider 종류

Provider 종류 특징 사용 예시
Provider 읽기 전용 데이터 제공 (상태 변경 X) 상수 데이터, 계산된 값 전달
StateProvider 간단한 상태 변경 관리 (int, bool 등) Counter, ON/OFF 버튼
FutureProvider 비동기 데이터 관리 (API 요청 등) 서버 통신, 데이터 로드
StreamProvider 실시간 스트림 데이터 관리 WebSocket, 채팅
NotifierProvider 복잡한 상태 관리 (MVVM의 ViewModel에 적합) CRUD, 데이터 가공 로직
AutoDispose 위 Provider에 .autoDispose 추가 가능 (메모리 절약) 일회성 데이터 로드, 화면 전환 시 데이터 해제

 

 

Notifier 종류

Notifier는 NotifierProvider와 함께 사용하여 복잡한 상태관리 가능. 상태 클래스 선언이 가능하고, UI 업데이트, 데이터 처리에 최적화

Notifier 종류 특징 사용 예시
Notifier 일반 상태 관리 도구 (싱글톤 구조) 복잡한 상태 로직
AsyncNotifier 비동기 상태 관리 도구 API 호출, 데이터 로드
AutoDisposeNotifier 화면 종료 시 자동 해제 화면이 바뀌는 경우 메모리 절약
AutoDisposeAsyncNotifier 비동기 상태 + 자동 해제 관리 일회성 비동기 작업 (예: 검색 결과)
FamilyNotifier 매개변수를 받는 Notifier 사용자 ID 기반 데이터 로드
AutoDisposeFamilyNotifier 매개변수 + 자동 해제 관리 사용자 ID별 비동기 데이터 로드

 


예시 코드

 

FutureProvider(비동기 데이터 관리)

// 비동기 String 타입의 데이터를 관리하는 weatherProvider 정의
final weatherProvider = FutureProvider<String>((ref) async {
  await Future.delayed(Duration(seconds: 2)); // 2초 딜레이 후 데이터 반환
  return "맑음"; // "맑음" 이라는 문자열 데이터 반환
});

class WeatherScreen extends ConsumerWidget { // ComsumerWidget : Riverpod의 상태 관리 구독
  @override
  Widget build(BuildContext context, WidgetRef ref) { //WidgetRef ref를 통해 Provider 상태에 접근 가능
    // weatherProvier의 상태 변화를 감지하고 자동을 화면을 리빌드
    // FutureProvider는 비동기 상태라 AsyncValue 타입의 데이터 반환
    final weatherState = ref.watch(weatherProvider); 

    return Scaffold(
      appBar: AppBar(title: Text('FutureProvider 예제')),
      // when()은 AsyncValue에서 제공하는 메서드, 비동기 상태에 따라 UI를 다르게 표시할 수 있음
      body: weatherState.when(
        data: (weather) => Center(child: Text('오늘의 날씨: $weather')), // 성공 시 데이터를 받아 화면에 표시
        loading: () => Center(child: CircularProgressIndicator()), // 로딩 시 로딩 스피너 표시
        error: (error, _) => Center(child: Text('에러 발생: $error')), // 에러 발생 시 에러 메세지 출력
      ),
    );
  }
}

 

NotifierProvier(복잡한 상태 관리)

// Notifier를 상속받아 상태 관리를 담당하는 ViewModel
class CounterNotifier extends Notifier<int> { // 상태의 데이터 타입 : int 
  @override
  int build() => 0; // 0으로 초기화

  // state는 현재 상태 값을 의미, Notifier에서 제공하는 기본 상태 관리 변수
  // state 값이 변경되면 Notifier가 이를 감지하고 자동으로 UI 업데이트
  void increment() => state++;
  void decrement() => state--; 
}

// counterProvider 정의
// 위에서 선언한 CounterNotifier를 통해 int 타입의 상태를 관리하는 Provier
// CounterNotifier.new를 통해 CounterNotifier의 인스턴스 생성
final counterProvider = NotifierProvider<CounterNotifier, int>(CounterNotifier.new);


// View(UI)
class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // counterProvier의 상태(int)를 구독
    // count의 값이 바뀔 때 마다 UI가 자동으로 빌드(ref.watch 특징)
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: Text('NotifierProvider 예제')),
      body: Center(child: Text('Count: $count')),
      floatingActionButton: FloatingActionButton(
      // 사용자가 버튼을 누르면 counterProvider.notifier(CounterNotifier)의 increment()가 호출됨
      // 버튼을 누를 때마다 0에서부터 1씩 값이 증가
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: Icon(Icons.add),
      ),
    );
  }
}

 

ref.watch()와 ref.read()

사용 방법 역할 사용 예시
ref.watch(provider) 상태를 자동으로 구독하여 UI 업데이트 UI에서 사용 (화면 리빌드 필요 시)
ref.read(provider) 상태를 한 번만 읽거나, .notifier 호출 시 사용 이벤트 핸들러, 버튼 클릭 시
ref.read(provider.notifier) 상태를 변경할 때 사용, ref.read()에서만 사용 상태 변경 시 필수