Riverpod — Flutter를 위한 컴파일된 의존성 관리

저자: IT Sectr 게시일: 2026-02-19 읽는 시간: 7 분

Riverpod — Flutter를 위한 컴파일된 상태 및 의존성 관리자로, 2021년 Remi Rousselet이 Provider의 후속으로 만들었습니다. Riverpod는 Provider의 근본적인 문제를 해결합니다: 컴파일 타임 검사 부족, BuildContext 의존성, ProviderNotFoundException의 복잡성.pub.dev에 따르면, 이 패키지는 5천 개 이상의 좋아요를 받았으며 새로운 프로젝트에서 Provider를 적극적으로 대체하고 있습니다.

핵심 사항

  • ProviderRef — 프로바이더 내부에서 다른 프로바이더에 접근하기 위한 객체
  • AsyncValue — loading/error/data 상태를 가진 비동기 데이터용 래퍼
  • Notifier — 변경 메서드를 가진 변경 가능한 상태 클래스
  • ProviderScope — 모든 프로바이더를 관리하는 루트 위젯
  • Code Generation — 자동 프로바이더 생성을 위한 @riverpod 애노테이션

Riverpod란?

Riverpod는 프로바이더 설명을 안전한 Dart 코드로 컴파일하는 Flutter용 상태 관리 및 의존성 주입 라이브러리입니다. Provider와 달리, Riverpod 프로바이더는 BuildContext에 묶이지 않습니다: 전역적으로 또는 ProviderScope 내에서 생성되며 어디서나 접근 가능합니다. 컴파일러는 빌드 타임에 타입, 의존성 및 프로바이더 그래프의 무결성을 검사하여 ProviderNotFoundException과 같은 런타임 오류를 제거합니다.

Riverpod는 테스트에 override 모델을 사용합니다: 각 프로바이더는 서브클래스를 만들거나 인터페이스를 모킹하지 않고 ProviderScope.overrideWith를 통해 재정의할 수 있습니다. 이렇게 하면 테스트가 격리됩니다: 각 테스트는 완전히 제어되는 의존성 그래프의 자체 복사본을 얻습니다.

Flutter Community Survey 2025에 따르면, Riverpod는 Provider와 BLoC 다음으로 인기 3위를 차지하고 있습니다. 그러나 Riverpod는 가장 빠르게 성장하는 패키지입니다: 2024년에 +120% 설치 증가. 주요 이유: 컴파일 타임 안전성, ProviderNotFoundException 없음, AsyncValue를 통한 내장 비동기 지원.

프로바이더 유형

Riverpod는 8가지 유형의 프로바이더를 제공하며, 각각 특정 시나리오에 맞습니다: Provider (상수/서비스), StateProvider (원시 상태), StateNotifierProvider (StateNotifier를 사용한 복잡한 로직), ChangeNotifierProvider (Provider에서 마이그레이션용), FutureProvider (비동기 데이터, 한 번), StreamProvider (반응형 스트림), NotifierProvider (새 API, Flutter 3.10+) 및 AsyncNotifierProvider (비동기 Notifier).

Dart
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
  return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void increment() => state++;
  void decrement() => state--;
}

class CounterScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Text('$count');
  }
}

ProviderRef — 다른 프로바이더에 접근하기 위해 각 프로바이더에 전달되는 객체. ref.watch — 변경 구독, ref.read — 일회성 읽기, ref.invalidate — 캐시 재설정. ProviderRef는 Provider의 BuildContext를 대체합니다: 모든 프로바이더가 위젯 트리에 접근하지 않고 다른 프로바이더를 읽을 수 있습니다. 이를 통해 UI 레이어 외부에서 의존성 그래프를 구축할 수 있습니다.

ProviderScope — Riverpod가 작동하는 데 필수적인 루트 위젯. ProviderScope는 모든 프로바이더를 저장하고, 라이프사이클을 관리하며 값을 캐시합니다. ProviderScope가 없으면 앱이 ProviderNotFoundException과 함께 충돌합니다. ProviderScope는 중첩될 수 있으며, 중첩된 범위는 부모 프로바이더를 재정의하며 테스트 및 기능 격리에 사용됩니다.

AsyncValue와 비동기 작업

AsyncValue — 비동기 상태를 표현하기 위한 Riverpod의 봉인된 클래스. AsyncValue에는 세 가지 변형이 있습니다: AsyncData (성공 데이터), AsyncError (오류), AsyncLoading (로딩). loading/error/data 사이를 수동으로 전환하는 대신, 각 FutureProvider 또는 StreamProvider가 자동으로 AsyncValue를 반환하고 위젯이 ref.watch를 통해 세 가지 상태를 모두 처리합니다.

Dart
final userProvider = FutureProvider((ref) async {
  final api = ref.watch(apiProvider);
  return await api.fetchUser();
});

class UserScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userProvider);
    return userAsync.when(
      data: (user) => UserWidget(user),
      error: (e, _) => ErrorWidget(e.toString()),
      loading: () => CircularProgressIndicator(),
    );
  }
}

AsyncValue.when — 세 가지 상태를 모두 패턴 매칭하는 메서드. 컴파일러는 세 가지 경우가 모두 처리되었는지 확인합니다 — loading 또는 error를 잊으면 코드가 컴파일되지 않습니다. AsyncValue.whenData — 데이터 전용 (loading/error가 필요하지 않은 경우). AsyncValue.guard — 예외를 AsyncError로 변환하기 위한 try-catch 래퍼. keepAlive — 프로바이더 캐시가 범위를 벗어날 때 파괴되는 것을 방지하는 플래그.

코드 생성과 @riverpod

코드 생성 — Riverpod 2.0+의 핵심 기능입니다. 함수에 @riverpod 애노테이션을 추가하면 올바른 타입, 리팩토링 지원 및 자동 완성을 갖춘 프로바이더가 자동으로 생성됩니다. 코드 생성은 riverpod_generatorbuild_runner를 사용합니다. 개발자가 순수 함수를 작성하면 타입, 클래스, 팩토리 생성자 등 나머지 모든 것이 자동으로 생성됩니다.

Dart
@riverpod
String helloWorld(HelloWorldRef ref) {
  return 'Hello World';
}

// 생성됨: final helloWorldProvider = Provider((ref) => 'Hello World');

@riverpod
class Counter extends _$Counter {
  int build() => 0;
  void increment() => state++;
}

Notifier — 코드 생성과 함께 변경 가능한 상태를 위한 새 API. Notifier는 build() 메서드와 상태 변경 메서드를 가진 클래스입니다. StateNotifier와 달리 Notifier는 별도의 상태 클래스가 필요하지 않으며 getter/setter를 통해 state에 직접 접근할 수 있습니다. Riverpod는 @riverpod로 애노테이션된 각 Notifier 클래스에 대해 자동으로 NotifierProvider를 생성합니다.

build_runner: 코드 생성은 dart run build_runner build 명령으로 실행됩니다. 생성된 파일은 .g.dart 접미사를 가지며 소스 코드로 가져옵니다. 애노테이션이나 프로바이더 타입이 변경되면 코드 생성을 다시 실행해야 합니다. Riverpod 2.x는 모든 새 프로젝트에 코드 생성을 권장합니다 — 수동 프로바이더 생성은 구식이 되어가고 있습니다.

Riverpod vs Provider

주요 차이점 Riverpod와 Provider 간: BuildContext로부터의 독립성, 컴파일 타임 안전성, 내장 비동기 지원, 자동 캐싱 및 override를 통한 테스트. Provider는 상태에 접근하기 위해 BuildContext가 필요합니다 (context.watch, context.read), Riverpod는 WidgetRef와 전역적으로 선언된 프로바이더를 사용합니다.

특성ProviderRiverpod
BuildContext 의존성아니요
컴파일 타임 검사아니요예 (@riverpod 통해)
ProviderNotFoundException런타임불가능
비동기수동AsyncValue (내장)
테스트Provider 래퍼ProviderScope.overrideWith
캐싱아니요자동 + keepAlive

Provider에서 마이그레이션: Riverpod는 재작성 없이 기존 ChangeNotifier를 사용하기 위한 ChangeNotifierProvider.adaptive를 지원합니다. 점진적 마이그레이션: 먼저 새 기능을 Riverpod로 작성한 다음, 어댑터를 통해 기존 Provider 인스턴스를 Riverpod 프로바이더로 교체합니다. 두 패키지 모두 한 프로젝트에서 공존할 수 있어 개발을 중단하지 않고 마이그레이션할 수 있습니다.

Riverpod 테스트

Riverpod 테스트는 ProviderScope.overrideWith를 기반으로 합니다. 각 프로바이더는 목(Mock)이나 DI 컨테이너 없이 테스트 ProviderScope 내에서 재정의됩니다. ProviderContainer — Flutter 없이 테스트를 위한 격리된 환경 (순수 Dart), 위젯 렌더링 없이 프로바이더를 테스트할 수 있습니다.

Dart
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';

void main() {
  test('Counter increments correctly', () {
    final container = ProviderContainer();
    container.read(counterProvider.notifier).increment();
    expect(container.read(counterProvider), 1);
  });

  testWidgets('UI updates on increment', (tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [counterProvider.overrideWithValue(5)],
        child: CounterScreen(),
      ),
    );
    expect(find.text('5'), findsOneWidget);
  });
}

ProviderContainer — Flutter 없음. 위젯 없이 프로바이더의 단위 테스트에는 ProviderContainer를 사용하세요. overrideWithValue — 특정 값으로 프로바이더 교체. overrideWith — 프로바이더 팩토리로 교체 (서비스 모킹용). autodispose — 테스트에서 container.dispose()를 사용하여 프로바이더가 범위를 벗어날 때 파괴되는지 확인하세요.

자주 묻는 질문

Riverpod는 BLoC와 어떻게 다른가요?

Riverpod는 전역 프로바이더, AsyncValue 및 코드 생성을 갖춘 상태 관리 라이브러리입니다. BLoC는 Event → Stream → State를 사용하는 아키텍처 패턴입니다. Riverpod는 배우기 쉽고 @riverpod 애노테이션을 통해 더 나은 DX를 제공합니다. BLoC는 엄격한 비즈니스 로직 격리와 BlocObserver를 통한 이벤트 추적을 제공합니다. 선택은 프로젝트 패러다임에 따라 다릅니다: Riverpod는 Provider에 가깝고, BLoC는 반응형 스트림에 가깝습니다.

Riverpod에서 autodispose란 무엇인가요?

Autodispose는 아무도 구독하지 않은 프로바이더를 자동으로 파괴하는 메커니즘입니다. 기본적으로 모든 Riverpod 프로바이더는 autodispose합니다: 위젯이 트리를 벗어나면 프로바이더가 메모리에서 제거됩니다. keepAlive — 항상 유지되어야 하는 프로바이더 (API 클라이언트, 리포지토리, 설정)의 autodispose를 비활성화하는 플래그입니다. 이는 메모리 누수를 방지합니다 — 사용되지 않는 프로바이더는 자동으로 파괴됩니다.

ref.invalidate는 어떻게 작동하나요?

ref.invalidate — 프로바이더 캐시를 강제로 재설정하는 메서드입니다. invalidate 후 다음 읽기에서 프로바이더가 다시 생성됩니다: FutureProvider가 비동기 함수를 다시 실행하고, StreamProvider가 스트림을 다시 구독합니다. 데이터 새로고침을 강제하려면 invalidate를 사용하세요 (당겨서 새로고침, 사용자 전환). ref.refresh — invalidate + 읽기의 조합: 한 번의 작업으로 재설정하고 즉시 새 값을 읽습니다.

코드 생성 없이 Riverpod를 사용할 수 있나요?

네. Riverpod 1.x는 코드 생성 없이만 작동합니다 — 프로바이더는 Provider(), StateNotifierProvider(), FutureProvider() 등을 사용하여 수동으로 생성됩니다. Riverpod 2.x는 두 접근 방식을 모두 지원합니다. 코드 생성 없이 보일러플레이트는 더 많지만 build_runner 및 dart run build_runner build에 대한 의존성은 없습니다. 소규모 프로젝트(최대 30개 프로바이더)의 경우 수동 생성이 합리적이며, 대규모 프로젝트의 경우 코드 생성이 필수입니다.

Family 프로바이더란 무엇인가요?

Family — 외부 매개변수를 받는 프로바이더 수정자입니다. 예를 들어, userProvider(123) — ID 123의 사용자를 로드하는 프로바이더입니다. Family 프로바이더는 각 고유 매개변수에 대해 개별적으로 결과를 캐시합니다. 각 항목이 ID로 로드되는 항목 목록에는 Family를 사용하세요. Family 수정자는 모든 프로바이더 유형에서 사용 가능합니다: Provider.family, FutureProvider.family, StreamProvider.family.

요약

  • Riverpod — ProviderNotFoundException 없는 컴파일된 상태 관리자, Provider의 후속
  • ProviderRef — 다른 프로바이더 내에서 프로바이더에 접근하기 위한 BuildContext 대체
  • AsyncValue — 비동기 데이터를 위한 loading/error/data 상태를 가진 봉인된 클래스
  • @riverpod 코드 생성 — 자동 타입 추론 및 프로바이더 팩토리
  • ProviderScope.overrideWith — 목 및 DI 컨테이너 없는 격리된 테스트
  • Family — 개별 캐싱을 가진 매개변수화된 프로바이더
  • autodispose 및 keepAlive — 자동 프로바이더 라이프사이클 관리

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기