# Riverpod 3.0 Provider Architecture This directory contains core-level providers that are used across the application. ## Directory Structure ``` lib/core/providers/ ├── connectivity_provider.dart # Network connectivity monitoring ├── provider_examples.dart # Comprehensive Riverpod 3.0 examples └── README.md # This file ``` ## Setup Instructions ### 1. Install Dependencies Dependencies are already configured in `pubspec.yaml`: ```yaml dependencies: flutter_riverpod: ^3.0.0 riverpod_annotation: ^3.0.0 dev_dependencies: build_runner: ^2.4.11 riverpod_generator: ^3.0.0 riverpod_lint: ^3.0.0 custom_lint: ^0.7.0 ``` Run to install: ```bash flutter pub get ``` ### 2. Generate Code Run code generation whenever you create or modify providers: ```bash # Watch mode (auto-regenerates on changes) dart run build_runner watch -d # One-time build dart run build_runner build -d # Clean and rebuild dart run build_runner build --delete-conflicting-outputs ``` ### 3. Wrap App with ProviderScope In `main.dart`: ```dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; void main() { runApp( const ProviderScope( child: MyApp(), ), ); } ``` ## Riverpod 3.0 Key Concepts ### @riverpod Annotation The `@riverpod` annotation is the core of code generation. It automatically generates the appropriate provider type based on your function/class signature. ```dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'my_provider.g.dart'; // Simple value @riverpod String myValue(MyValueRef ref) => 'Hello'; // Async value @riverpod Future myAsync(MyAsyncRef ref) async => 'Hello'; // Stream @riverpod Stream myStream(MyStreamRef ref) => Stream.value(1); // Mutable state @riverpod class MyNotifier extends _$MyNotifier { @override int build() => 0; void increment() => state++; } // Async mutable state @riverpod class MyAsyncNotifier extends _$MyAsyncNotifier { @override Future build() async => 'Initial'; Future update(String value) async { state = await AsyncValue.guard(() async => value); } } ``` ### Provider Types (Auto-Generated) 1. **Simple Provider** - Immutable value - Function returning T → Provider 2. **FutureProvider** - Async value - Function returning Future → FutureProvider 3. **StreamProvider** - Stream of values - Function returning Stream → StreamProvider 4. **NotifierProvider** - Mutable state with methods - Class extending Notifier → NotifierProvider 5. **AsyncNotifierProvider** - Async mutable state - Class extending AsyncNotifier → AsyncNotifierProvider 6. **StreamNotifierProvider** - Stream mutable state - Class extending StreamNotifier → StreamNotifierProvider ### Family (Parameters) In Riverpod 3.0, family is just function parameters! ```dart // Old way (Riverpod 2.x) final userProvider = FutureProvider.family((ref, id) async { return fetchUser(id); }); // New way (Riverpod 3.0) @riverpod Future user(UserRef ref, String id) async { return fetchUser(id); } // Multiple parameters (named, optional, defaults) @riverpod Future> posts( PostsRef ref, { required String userId, int page = 1, int limit = 20, String? category, }) async { return fetchPosts(userId, page, limit, category); } // Usage ref.watch(userProvider('user123')); ref.watch(postsProvider(userId: 'user123', page: 2, category: 'tech')); ``` ### AutoDispose vs KeepAlive ```dart // AutoDispose (default) - cleaned up when not used @riverpod String autoExample(AutoExampleRef ref) => 'Auto disposed'; // KeepAlive - stays alive until app closes @Riverpod(keepAlive: true) String keepExample(KeepExampleRef ref) => 'Kept alive'; ``` ### Unified Ref Type Riverpod 3.0 uses a single `Ref` type (no more FutureProviderRef, StreamProviderRef, etc.): ```dart @riverpod Future example(ExampleRef ref) async { // All providers use the same ref type ref.watch(otherProvider); ref.read(anotherProvider); ref.listen(thirdProvider, (prev, next) {}); ref.invalidate(fourthProvider); } ``` ### ref.mounted Check Always check `ref.mounted` after async operations in Notifiers: ```dart @riverpod class Example extends _$Example { @override String build() => 'Initial'; Future updateData() async { await Future.delayed(Duration(seconds: 2)); // Check if provider is still mounted if (!ref.mounted) return; state = 'Updated'; } } ``` ### Error Handling with AsyncValue.guard() ```dart @riverpod class Data extends _$Data { @override Future build() async => 'Initial'; Future update(String value) async { state = const AsyncValue.loading(); // AsyncValue.guard catches errors automatically state = await AsyncValue.guard(() async { await api.update(value); return value; }); } } ``` ## 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 void initState() { super.initState(); // ref is available in all lifecycle methods } @override Widget build(BuildContext context) { final value = ref.watch(myProvider); return Text(value); } } ``` ### Consumer (for optimization) ```dart Column( children: [ const Text('Static'), Consumer( builder: (context, ref, child) { final count = ref.watch(counterProvider); return Text('$count'); }, ), ], ) ``` ## Best Practices ### 1. Use .select() for Optimization ```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)); // Good - rebuilds only when async value has data final userName = ref.watch( userProvider.select((async) => async.value?.name), ); ``` ### 2. Provider Dependencies ```dart @riverpod Future dashboard(DashboardRef ref) async { // Watch other providers final user = await ref.watch(userProvider.future); final posts = await ref.watch(postsProvider.future); return Dashboard(user: user, posts: posts); } ``` ### 3. Invalidation and Refresh ```dart // In a widget or notifier ref.invalidate(userProvider); // Invalidate ref.refresh(userProvider); // Invalidate and re-read ``` ### 4. Lifecycle Hooks ```dart @riverpod String example(ExampleRef ref) { ref.onDispose(() { // Clean up }); ref.onCancel(() { // Last listener removed }); ref.onResume(() { // New listener added after cancel }); return 'value'; } ``` ### 5. 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); }); test('async provider', () async { final container = ProviderContainer(); addTearDown(container.dispose); final value = await container.read(userProvider.future); expect(value.name, 'John'); }); ``` ## Migration from Riverpod 2.x ### StateNotifierProvider → NotifierProvider ```dart // Old (2.x) class Counter extends StateNotifier { Counter() : super(0); void increment() => state++; } final counterProvider = StateNotifierProvider(Counter.new); // New (3.0) @riverpod class Counter extends _$Counter { @override int build() => 0; void increment() => state++; } ``` ### FutureProvider.family → Function with Parameters ```dart // Old (2.x) final userProvider = FutureProvider.family((ref, id) async { return fetchUser(id); }); // New (3.0) @riverpod Future user(UserRef ref, String id) async { return fetchUser(id); } ``` ### Ref Types → Single Ref ```dart // Old (2.x) final provider = FutureProvider((FutureProviderRef ref) async { return 'value'; }); // New (3.0) @riverpod Future provider(ProviderRef ref) async { return 'value'; } ``` ## Common Patterns See `provider_examples.dart` for comprehensive examples of: - Simple providers - Async providers (FutureProvider pattern) - Stream providers - Notifier (mutable state) - AsyncNotifier (async mutable state) - StreamNotifier - Family (parameters) - Provider composition - Error handling - Lifecycle hooks - Optimization with .select() ## Riverpod Lint Rules The project is configured with `riverpod_lint` for additional checks: - `provider_dependencies` - Ensure proper dependency usage - `scoped_providers_should_specify_dependencies` - Scoped provider safety - `avoid_public_notifier_properties` - Encapsulation - `avoid_ref_read_inside_build` - Performance - `avoid_manual_providers_as_generated_provider_dependency` - Use generated providers - `functional_ref` - Proper ref usage - `notifier_build` - Proper Notifier implementation Run linting: ```bash dart run custom_lint ``` ## Resources - [Riverpod Documentation](https://riverpod.dev) - [Code Generation Guide](https://riverpod.dev/docs/concepts/about_code_generation) - [Migration Guide](https://riverpod.dev/docs/migration/from_state_notifier) - [Provider Examples](./provider_examples.dart)