Riverpod — Flutter를 위한 컴파일된 상태 및 의존성 관리자로, 2021년 Remi Rousselet이 Provider의 후속으로 만들었습니다. Riverpod는 Provider의 근본적인 문제를 해결합니다: 컴파일 타임 검사 부족, BuildContext 의존성, ProviderNotFoundException의 복잡성.pub.dev에 따르면, 이 패키지는 5천 개 이상의 좋아요를 받았으며 새로운 프로젝트에서 Provider를 적극적으로 대체하고 있습니다.
핵심 사항
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).
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 — 비동기 상태를 표현하기 위한 Riverpod의 봉인된 클래스. AsyncValue에는 세 가지 변형이 있습니다: AsyncData (성공 데이터), AsyncError (오류), AsyncLoading (로딩). loading/error/data 사이를 수동으로 전환하는 대신, 각 FutureProvider 또는 StreamProvider가 자동으로 AsyncValue를 반환하고 위젯이 ref.watch를 통해 세 가지 상태를 모두 처리합니다.
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 2.0+의 핵심 기능입니다. 함수에 @riverpod 애노테이션을 추가하면 올바른 타입, 리팩토링 지원 및 자동 완성을 갖춘 프로바이더가 자동으로 생성됩니다. 코드 생성은 riverpod_generator와 build_runner를 사용합니다. 개발자가 순수 함수를 작성하면 타입, 클래스, 팩토리 생성자 등 나머지 모든 것이 자동으로 생성됩니다.
@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와 Provider 간: BuildContext로부터의 독립성, 컴파일 타임 안전성, 내장 비동기 지원, 자동 캐싱 및 override를 통한 테스트. Provider는 상태에 접근하기 위해 BuildContext가 필요합니다 (context.watch, context.read), Riverpod는 WidgetRef와 전역적으로 선언된 프로바이더를 사용합니다.
| 특성 | Provider | Riverpod |
|---|---|---|
| BuildContext 의존성 | 예 | 아니요 |
| 컴파일 타임 검사 | 아니요 | 예 (@riverpod 통해) |
| ProviderNotFoundException | 런타임 | 불가능 |
| 비동기 | 수동 | AsyncValue (내장) |
| 테스트 | Provider 래퍼 | ProviderScope.overrideWith |
| 캐싱 | 아니요 | 자동 + keepAlive |
Provider에서 마이그레이션: Riverpod는 재작성 없이 기존 ChangeNotifier를 사용하기 위한 ChangeNotifierProvider.adaptive를 지원합니다. 점진적 마이그레이션: 먼저 새 기능을 Riverpod로 작성한 다음, 어댑터를 통해 기존 Provider 인스턴스를 Riverpod 프로바이더로 교체합니다. 두 패키지 모두 한 프로젝트에서 공존할 수 있어 개발을 중단하지 않고 마이그레이션할 수 있습니다.
Riverpod 테스트는 ProviderScope.overrideWith를 기반으로 합니다. 각 프로바이더는 목(Mock)이나 DI 컨테이너 없이 테스트 ProviderScope 내에서 재정의됩니다. ProviderContainer — Flutter 없이 테스트를 위한 격리된 환경 (순수 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는 전역 프로바이더, AsyncValue 및 코드 생성을 갖춘 상태 관리 라이브러리입니다. BLoC는 Event → Stream → State를 사용하는 아키텍처 패턴입니다. Riverpod는 배우기 쉽고 @riverpod 애노테이션을 통해 더 나은 DX를 제공합니다. BLoC는 엄격한 비즈니스 로직 격리와 BlocObserver를 통한 이벤트 추적을 제공합니다. 선택은 프로젝트 패러다임에 따라 다릅니다: Riverpod는 Provider에 가깝고, BLoC는 반응형 스트림에 가깝습니다.
Autodispose는 아무도 구독하지 않은 프로바이더를 자동으로 파괴하는 메커니즘입니다. 기본적으로 모든 Riverpod 프로바이더는 autodispose합니다: 위젯이 트리를 벗어나면 프로바이더가 메모리에서 제거됩니다. keepAlive — 항상 유지되어야 하는 프로바이더 (API 클라이언트, 리포지토리, 설정)의 autodispose를 비활성화하는 플래그입니다. 이는 메모리 누수를 방지합니다 — 사용되지 않는 프로바이더는 자동으로 파괴됩니다.
ref.invalidate — 프로바이더 캐시를 강제로 재설정하는 메서드입니다. invalidate 후 다음 읽기에서 프로바이더가 다시 생성됩니다: FutureProvider가 비동기 함수를 다시 실행하고, StreamProvider가 스트림을 다시 구독합니다. 데이터 새로고침을 강제하려면 invalidate를 사용하세요 (당겨서 새로고침, 사용자 전환). ref.refresh — invalidate + 읽기의 조합: 한 번의 작업으로 재설정하고 즉시 새 값을 읽습니다.
네. Riverpod 1.x는 코드 생성 없이만 작동합니다 — 프로바이더는 Provider(), StateNotifierProvider(), FutureProvider() 등을 사용하여 수동으로 생성됩니다. Riverpod 2.x는 두 접근 방식을 모두 지원합니다. 코드 생성 없이 보일러플레이트는 더 많지만 build_runner 및 dart run build_runner build에 대한 의존성은 없습니다. 소규모 프로젝트(최대 30개 프로바이더)의 경우 수동 생성이 합리적이며, 대규모 프로젝트의 경우 코드 생성이 필수입니다.
Family — 외부 매개변수를 받는 프로바이더 수정자입니다. 예를 들어, userProvider(123) — ID 123의 사용자를 로드하는 프로바이더입니다. Family 프로바이더는 각 고유 매개변수에 대해 개별적으로 결과를 캐시합니다. 각 항목이 ID로 로드되는 항목 목록에는 Family를 사용하세요. Family 수정자는 모든 프로바이더 유형에서 사용 가능합니다: Provider.family, FutureProvider.family, StreamProvider.family.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.