Flutter Riverpod 3.0 실전 개편 가이드: Notifier, AsyncNotifier와 자동 메모리 해제 패턴

Flutter Riverpod 3.0 실전 개편 가이드: Notifier, AsyncNotifier와 자동 메모리 해제 패턴
Flutter의 가장 대표적인 상태 관리 라이브러리인 Riverpod이 3.0 버전으로의 대대적인 아키텍처 개편을 맞이했습니다. 기존 1.x / 2.x 버전에서 혼용되던 StateNotifier, ChangeNotifier 및 주석 기반 코드 생성 방식이 클래스 기반 Notifier/AsyncNotifier 및 riverpod_generator 전면 표준화로 정립되었습니다.
참고로 @riverpod 코드 생성 방식은 이미 Riverpod 2.0부터 기본적으로 autoDispose(화면 이탈 시 자동 메모리 해제) 정책을 적용해 왔으며, 3.0에서는 여기에 더해 AutoDispose/Family 등으로 파편화되어 있던 프로바이더 타입 인터페이스를 Provider/Notifier로 통합(unify)하여 API가 한층 단순해졌습니다.
이번 글에서는 Riverpod 3.0 개편 핵심 포인트와 Notifier, AsyncNotifier, family 파라미터를 활용한 프로덕션 상태 관리 구현 패턴을 살펴봅니다.
1. Riverpod 3.0 주요 변경 사항 비교
| 구분 | Riverpod 2.x 이하 (Legacy) | Riverpod 3.0 표준 |
|---|---|---|
| 타입 인터페이스 | Provider / AutoDisposeProvider / FamilyProvider 등으로 세분화 |
Provider/Notifier로 통합 (autoDispose 기본 적용은 @riverpod 코드 생성 방식에서 2.0부터 동일) |
| 상태 관리 주체 | StateNotifierProvider / FutureProvider |
Notifier / AsyncNotifier (@riverpod 어노테이션) |
| 인자 전달 (Family) | .family 수동 래퍼 작성 |
메서드 파라미터 형태로 자동 생성 |
| 비동기 상태 분기 | AsyncValue 수동 when() 분기 처리 |
AsyncValue.when() 및 신규 AsyncValue 헬퍼 메서드 고도화 |
2. Notifier 및 AsyncNotifier 실전 구현
1) 동기 상태 관리: Notifier 구현
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter_provider.g.dart';
// @riverpod 어노테이션 사용 시 기본적으로 autoDispose 적용
@riverpod
class CounterNotifier extends _$CounterNotifier {
@override
int build() {
// 초기 상태 반환 (화면 재진입 시 build 재실행)
return 0;
}
void increment() {
state = state + 1;
}
void decrement() {
state = state - 1;
}
}
2) 비동기 상태 관리: AsyncNotifier & Family 구현
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../models/user.dart';
part 'user_provider.g.dart';
// family 파라미터를 build 메서드의 인자로 직접 전달
@riverpod
class UserDetailNotifier extends _$UserDetailNotifier {
@override
Future<User> build(String userId) async {
// 비동기 데이터 페칭
return await ref.read(userRepositoryProvider).fetchUser(userId);
}
Future<void> updateUserName(String newName) async {
// 로딩 상태로 전환
state = const AsyncLoading();
// 상태 업데이트 및 Guard 처리
state = await AsyncValue.guard(() async {
final updatedUser = await ref
.read(userRepositoryProvider)
.updateName(arg, newName); // arg는 family 인자 (userId)
return updatedUser;
});
}
}
3. Widget 계층에서의 AsyncValue 소비 및 autoDispose 대응
UI 단에서는 ref.watch를 통해 상태를 구독하고 AsyncValue.when으로 로딩/에러/성공 상태를 깔끔하게 분기합니다.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class UserProfileScreen extends ConsumerWidget {
final String userId;
const UserProfileScreen({super.key, required this.userId});
@override
Widget build(BuildContext context, WidgetRef ref) {
// family 인자와 함께 프로바이더 구독
final userAsync = ref.watch(userDetailNotifierProvider(userId));
return Scaffold(
appBar: AppBar(title: const Text('사용자 프로필')),
body: userAsync.when(
data: (user) => Column(
children: [
Text('이름: ${user.name}'),
ElevatedButton(
onPressed: () {
ref
.read(userDetailNotifierProvider(userId).notifier)
.updateUserName('새 이름');
},
child: const Text('이름 변경'),
),
],
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('에러 발생: $err')),
),
);
}
}
4. autoDispose 관련 실전 주의사항
- 글로벌 인증/설정 상태: 전역 로그인 정보나 테마 설정처럼 앱 생명주기 내내 유지되어야 하는 프로바이더는
@Riverpod(keepAlive: true)를 명시해야 합니다. ref.keepAlive()링크 활용: 특정 액션 수행 중에는 화면이 이탈하더라도 상태 해제를 유예하고 싶은 경우,build()내에서final link = ref.keepAlive();를 생성하고 완료 후link.close()로 해제합니다.- 가비지 컬렉션 타이밍:
autoDispose는 위젯 트리에서 마지막 소비자(watch)가 사라져도 즉시 파기하지 않고 한 프레임(1 frame)의 유예 기간을 기다린 뒤, 그 시점에도 여전히 사용되지 않을 때 상태를 파기하므로 폼 입력 임시 저장 시 캐싱 전략을 고려해야 합니다.
요약 및 결론
Riverpod 3.0은 보일러플레이트 코드를 최소화하고 안전한 메모리 관리를 기본 탑재한 모던 상태 관리 체계입니다.
[!NOTE]
StateNotifier대신 클래스 기반Notifier/AsyncNotifier로 전환하세요.riverpod_generator를 적극 활용하여family파라미터 및 프로바이더 생성을 자동화하세요.- 기본 적용된
autoDispose정책을 이해하고, 전역 상태는@Riverpod(keepAlive: true)로 명시하여 메모리 누수를 원천 차단하세요.
새로운 Riverpod 3.0 패턴을 적용하면Flutter 앱의 유지보수성과 타입 안정성을 한 차원 높일 수 있습니다.