# Riverpod 3.0 Quick Reference Card ## File Structure ```dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'my_provider.g.dart'; // REQUIRED! // Your providers here ``` ## Provider Types ### 1. Simple Value (Immutable) ```dart @riverpod String appName(AppNameRef ref) => 'Worker App'; // Usage final name = ref.watch(appNameProvider); ``` ### 2. Async Value (Future) ```dart @riverpod Future user(UserRef ref, String id) async { return await fetchUser(id); } // Usage final userAsync = ref.watch(userProvider('123')); userAsync.when( data: (user) => Text(user.name), loading: () => const CustomLoadingIndicator(), error: (e, _) => Text('Error: $e'), ); ``` ### 3. Stream ```dart @riverpod Stream messages(MessagesRef ref) { return webSocket.messages; } // Usage final messages = ref.watch(messagesProvider); ``` ### 4. Mutable State (Notifier) ```dart @riverpod class Counter extends _$Counter { @override int build() => 0; void increment() => state++; void decrement() => state--; } // Usage final count = ref.watch(counterProvider); ref.read(counterProvider.notifier).increment(); ``` ### 5. Async Mutable State (AsyncNotifier) ```dart @riverpod class Profile extends _$Profile { @override Future build() async { return await api.getProfile(); } Future update(String name) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { return await api.updateProfile(name); }); } } // Usage final profile = ref.watch(profileProvider); await ref.read(profileProvider.notifier).update('New Name'); ``` ## Family (Parameters) ```dart // Single parameter @riverpod Future post(PostRef ref, String id) async { return await api.getPost(id); } // Multiple parameters @riverpod Future> posts( PostsRef ref, { required String userId, int page = 1, String? category, }) async { return await api.getPosts(userId, page, category); } // Usage ref.watch(postProvider('post-123')); ref.watch(postsProvider(userId: 'user-1', page: 2)); ``` ## AutoDispose vs KeepAlive ```dart // AutoDispose (default) - cleaned up when not used @riverpod String temp(TempRef ref) => 'Auto disposed'; // KeepAlive - stays alive @Riverpod(keepAlive: true) String config(ConfigRef ref) => 'Global config'; ``` ## Usage in Widgets ### ConsumerWidget ```dart class MyWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final value = ref.watch(myProvider); return Text(value); } } ``` ### ConsumerStatefulWidget ```dart class MyWidget extends ConsumerStatefulWidget { @override ConsumerState createState() => _MyWidgetState(); } class _MyWidgetState extends ConsumerState { @override Widget build(BuildContext context) { final value = ref.watch(myProvider); return Text(value); } } ``` ### Consumer (optimization) ```dart Consumer( builder: (context, ref, child) { final count = ref.watch(counterProvider); return Text('$count'); }, ) ``` ## Ref Methods ### ref.watch() - Use in build ```dart // Rebuilds when value changes final value = ref.watch(myProvider); ``` ### ref.read() - Use in callbacks ```dart // One-time read, doesn't listen onPressed: () { ref.read(myProvider.notifier).update(); } ``` ### ref.listen() - Side effects ```dart ref.listen(authProvider, (prev, next) { if (next.isLoggedOut) { Navigator.of(context).pushReplacementNamed('/login'); } }); ``` ### ref.invalidate() - Force refresh ```dart ref.invalidate(userProvider); ``` ### ref.refresh() - Invalidate and read ```dart final newValue = ref.refresh(userProvider); ``` ## AsyncValue Handling ### .when() ```dart asyncValue.when( data: (value) => Text(value), loading: () => const CustomLoadingIndicator(), error: (error, stack) => Text('Error: $error'), ); ``` ### Pattern Matching (Dart 3+) ```dart switch (asyncValue) { case AsyncData(:final value): return Text(value); case AsyncError(:final error): return Text('Error: $error'); case AsyncLoading(): return const CustomLoadingIndicator(); } ``` ### Direct Checks ```dart if (asyncValue.isLoading) return Loading(); if (asyncValue.hasError) return Error(); final data = asyncValue.value!; ``` ## Performance Optimization ### Use .select() ```dart // Bad - rebuilds on any user change final user = ref.watch(userProvider); // Good - rebuilds only when name changes final name = ref.watch( userProvider.select((user) => user.name), ); // With AsyncValue final name = ref.watch( userProvider.select((async) => async.value?.name), ); ``` ## Error Handling ### AsyncValue.guard() ```dart @riverpod class Data extends _$Data { @override Future build() async => 'Initial'; Future update(String value) async { state = const AsyncValue.loading(); // Catches errors automatically state = await AsyncValue.guard(() async { return await api.update(value); }); } } ``` ## Provider Composition ```dart @riverpod Future dashboard(DashboardRef ref) async { // Depend on other providers final user = await ref.watch(userProvider.future); final posts = await ref.watch(postsProvider.future); return Dashboard(user: user, posts: posts); } ``` ## Lifecycle Hooks ```dart @riverpod String example(ExampleRef ref) { ref.onDispose(() { // Cleanup print('Disposed'); }); ref.onCancel(() { // Last listener removed }); ref.onResume(() { // New listener added }); return 'value'; } ``` ## ref.mounted Check (Riverpod 3.0) ```dart @riverpod class Example extends _$Example { @override String build() => 'Initial'; Future update() async { await Future.delayed(Duration(seconds: 2)); // Check if still mounted if (!ref.mounted) return; state = 'Updated'; } } ``` ## Code Generation Commands ```bash # Watch mode (recommended) dart run build_runner watch -d # One-time build dart run build_runner build --delete-conflicting-outputs # Clean and rebuild dart run build_runner clean && dart run build_runner build -d ``` ## Linting ```bash # Check Riverpod issues dart run custom_lint # Auto-fix dart run custom_lint --fix ``` ## Testing ```dart test('counter increments', () { final container = ProviderContainer(); addTearDown(container.dispose); expect(container.read(counterProvider), 0); container.read(counterProvider.notifier).increment(); expect(container.read(counterProvider), 1); }); ``` ## Widget Testing ```dart testWidgets('test', (tester) async { await tester.pumpWidget( ProviderScope( overrides: [ userProvider.overrideWith((ref) => User(name: 'Test')), ], child: MaterialApp(home: MyScreen()), ), ); expect(find.text('Test'), findsOneWidget); }); ``` ## Best Practices ✅ **DO:** - Use `ref.watch()` in build methods - Use `ref.read()` in event handlers - Use `.select()` to optimize rebuilds - Check `ref.mounted` after async operations - Use `AsyncValue.guard()` for error handling - Use autoDispose for temporary state - Keep providers in dedicated directories ❌ **DON'T:** - Use `ref.read()` in build methods - Forget the `part` directive - Use deprecated `StateNotifierProvider` - Create providers without code generation - Forget to run build_runner after changes ## Common Patterns ### Loading State ```dart Future save() async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() => api.save()); } ``` ### Pagination ```dart @riverpod class PostList extends _$PostList { @override Future> build() => _fetch(0); int _page = 0; Future loadMore() async { final current = state.value ?? []; _page++; state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { final newPosts = await _fetch(_page); return [...current, ...newPosts]; }); } Future> _fetch(int page) async { return await api.getPosts(page: page); } } ``` ### Form State ```dart @riverpod class LoginForm extends _$LoginForm { @override LoginFormState build() => LoginFormState(); void setEmail(String email) { state = state.copyWith(email: email); } void setPassword(String password) { state = state.copyWith(password: password); } Future submit() async { if (!state.isValid) return; state = state.copyWith(isLoading: true); try { await api.login(state.email, state.password); state = state.copyWith(isLoading: false, success: true); } catch (e) { state = state.copyWith(isLoading: false, error: e.toString()); } } } ``` ## Resources - 📄 provider_examples.dart - All patterns with examples - 📄 connectivity_provider.dart - Real-world implementation - 📄 RIVERPOD_SETUP.md - Complete guide - 🌐 https://riverpod.dev - Official documentation