# Riverpod 3.0 Setup - Worker Flutter App ## Overview This document provides a complete guide to the Riverpod 3.0 state management setup for the Worker Flutter app. ## What's Configured ### 1. Dependencies (pubspec.yaml) **Production Dependencies:** - `flutter_riverpod: ^3.0.0` - Main Riverpod package - `riverpod_annotation: ^3.0.0` - Annotations for code generation **Development Dependencies:** - `build_runner: ^2.4.11` - Code generation runner - `riverpod_generator: ^3.0.0` - Generates provider code from annotations - `riverpod_lint: ^3.0.0` - Riverpod-specific linting rules - `custom_lint: ^0.7.0` - Required for riverpod_lint ### 2. Build Configuration (build.yaml) Configured to generate code for: - `**_provider.dart` files - Files in `**/providers/` directories - Files in `**/notifiers/` directories ### 3. Analysis Options (analysis_options.yaml) Configured with: - Custom lint plugin enabled - Exclusion of generated files (*.g.dart, *.freezed.dart) - Riverpod-specific lint rules - Comprehensive code quality rules ### 4. App Initialization (main.dart) Wrapped with `ProviderScope`: ```dart void main() { runApp( const ProviderScope( child: MyApp(), ), ); } ``` ## Directory Structure ``` lib/core/providers/ ├── connectivity_provider.dart # Network connectivity monitoring ├── provider_examples.dart # Comprehensive Riverpod 3.0 examples └── README.md # Provider architecture documentation ``` ## Quick Start ### 1. Install Dependencies ```bash flutter pub get ``` ### 2. Generate Provider Code ```bash # One-time generation dart run build_runner build --delete-conflicting-outputs # Watch mode (auto-regenerates on file changes) dart run build_runner watch -d ``` ### 3. Use the Setup Script ```bash chmod +x scripts/setup_riverpod.sh ./scripts/setup_riverpod.sh ``` ## Core Providers ### Connectivity Provider Location: `/lib/core/providers/connectivity_provider.dart` **Purpose:** Monitor network connectivity status across the app. **Providers Available:** 1. **connectivityProvider** - Connectivity instance ```dart final connectivity = ref.watch(connectivityProvider); ``` 2. **connectivityStreamProvider** - Real-time connectivity stream ```dart final status = ref.watch(connectivityStreamProvider); status.when( data: (status) => Text('Status: $status'), loading: () => CircularProgressIndicator(), error: (e, _) => Text('Error: $e'), ); ``` 3. **currentConnectivityProvider** - One-time connectivity check ```dart final status = await ref.read(currentConnectivityProvider.future); ``` 4. **isOnlineProvider** - Boolean online/offline stream ```dart final isOnline = ref.watch(isOnlineProvider); ``` **Usage Example:** ```dart class MyWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final connectivityState = ref.watch(connectivityStreamProvider); return connectivityState.when( data: (status) { if (status == ConnectivityStatus.offline) { return OfflineBanner(); } return OnlineContent(); }, loading: () => LoadingIndicator(), error: (error, _) => ErrorWidget(error), ); } } ``` ## Riverpod 3.0 Key Features ### 1. @riverpod Annotation (Code Generation) The modern, recommended approach: ```dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'my_provider.g.dart'; // Simple value @riverpod String greeting(GreetingRef ref) => 'Hello'; // Async value @riverpod Future user(UserRef ref, String id) async { return await fetchUser(id); } // Mutable state @riverpod class Counter extends _$Counter { @override int build() => 0; void increment() => state++; } ``` ### 2. Unified Ref Type No more separate `FutureProviderRef`, `StreamProviderRef`, etc. - just `Ref`: ```dart @riverpod Future example(ExampleRef ref) async { ref.watch(provider1); ref.read(provider2); ref.listen(provider3, (prev, next) {}); } ``` ### 3. Family as Function Parameters ```dart // Simple parameter @riverpod Future user(UserRef ref, String id) async { return await fetchUser(id); } // Multiple parameters with named, optional, defaults @riverpod Future> posts( PostsRef ref, { required String userId, int page = 1, int limit = 20, String? category, }) async { return await fetchPosts(userId, page, limit, category); } // Usage ref.watch(userProvider('user123')); ref.watch(postsProvider(userId: 'user123', page: 2)); ``` ### 4. AutoDispose vs KeepAlive ```dart // AutoDispose (default) - cleaned up when not watched @riverpod String autoExample(AutoExampleRef ref) => 'Auto disposed'; // KeepAlive - stays alive until app closes @Riverpod(keepAlive: true) String keepExample(KeepExampleRef ref) => 'Kept alive'; ``` ### 5. ref.mounted Check New in Riverpod 3.0 - check if provider is still alive after async operations: ```dart @riverpod class DataManager extends _$DataManager { @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'; } } ``` ### 6. AsyncValue.guard() for Error Handling ```dart @riverpod class UserProfile extends _$UserProfile { @override Future build() async => await fetchUser(); Future update(String name) async { state = const AsyncValue.loading(); // AsyncValue.guard catches errors automatically state = await AsyncValue.guard(() async { return await updateUser(name); }); } } ``` ## Provider Patterns ### 1. Simple Provider (Immutable Value) ```dart @riverpod String appVersion(AppVersionRef ref) => '1.0.0'; @riverpod int pointsMultiplier(PointsMultiplierRef ref) { final tier = ref.watch(userTierProvider); return tier == 'diamond' ? 3 : 2; } ``` ### 2. FutureProvider (Async Data) ```dart @riverpod Future currentUser(CurrentUserRef ref) async { final token = await ref.watch(authTokenProvider.future); return await fetchUser(token); } ``` ### 3. StreamProvider (Real-time Data) ```dart @riverpod Stream> chatMessages(ChatMessagesRef ref, String roomId) { return ref.watch(webSocketProvider).messages(roomId); } ``` ### 4. Notifier (Mutable State) ```dart @riverpod class Cart extends _$Cart { @override List build() => []; void addItem(Product product) { state = [...state, CartItem.fromProduct(product)]; } void removeItem(String id) { state = state.where((item) => item.id != id).toList(); } void clear() { state = []; } } ``` ### 5. AsyncNotifier (Async Mutable State) ```dart @riverpod class UserProfile extends _$UserProfile { @override Future build() async { return await ref.read(userRepositoryProvider).getCurrentUser(); } Future updateName(String name) async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { final updated = await ref.read(userRepositoryProvider).updateName(name); return updated; }); } Future refresh() async { ref.invalidateSelf(); } } ``` ### 6. StreamNotifier (Stream Mutable State) ```dart @riverpod class LiveChat extends _$LiveChat { @override Stream> build(String roomId) { return ref.watch(chatServiceProvider).messagesStream(roomId); } Future sendMessage(String text) async { await ref.read(chatServiceProvider).send(roomId, text); } } ``` ## Usage in Widgets ### ConsumerWidget ```dart class ProductList extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final products = ref.watch(productsProvider); return products.when( data: (list) => ListView.builder( itemCount: list.length, itemBuilder: (context, index) => ProductCard(list[index]), ), loading: () => CircularProgressIndicator(), error: (error, stack) => ErrorView(error), ); } } ``` ### ConsumerStatefulWidget ```dart class OrderPage extends ConsumerStatefulWidget { @override ConsumerState createState() => _OrderPageState(); } class _OrderPageState extends ConsumerState { @override void initState() { super.initState(); // Can use ref in all lifecycle methods Future.microtask( () => ref.read(ordersProvider.notifier).loadOrders(), ); } @override Widget build(BuildContext context) { final orders = ref.watch(ordersProvider); return OrderList(orders); } } ``` ### Consumer (Optimization) ```dart Column( children: [ const StaticHeader(), Consumer( builder: (context, ref, child) { final count = ref.watch(cartCountProvider); return CartBadge(count); }, ), ], ) ``` ## Performance Optimization ### Use .select() to Watch Specific Fields ```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 with AsyncValue final userName = ref.watch( userProfileProvider.select((async) => async.value?.name), ); ``` ### Provider Composition ```dart @riverpod Future dashboard(DashboardRef ref) async { // Depend on other providers final user = await ref.watch(userProvider.future); final stats = await ref.watch(statsProvider.future); final orders = await ref.watch(recentOrdersProvider.future); return Dashboard( user: user, stats: stats, recentOrders: orders, ); } ``` ## Testing ### Unit Testing Providers ```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 fetches data', () async { final container = ProviderContainer(); addTearDown(container.dispose); final user = await container.read(userProvider.future); expect(user.name, 'John Doe'); }); ``` ### Widget Testing ```dart testWidgets('displays user name', (tester) async { await tester.pumpWidget( ProviderScope( overrides: [ userProvider.overrideWith((ref) => User(name: 'Test User')), ], child: MaterialApp(home: UserScreen()), ), ); expect(find.text('Test User'), findsOneWidget); }); ``` ### Mocking Providers ```dart testWidgets('handles loading state', (tester) async { await tester.pumpWidget( ProviderScope( overrides: [ userProvider.overrideWith((ref) { return Future.delayed( Duration(seconds: 10), () => User(name: 'Test'), ); }), ], child: MaterialApp(home: UserScreen()), ), ); expect(find.byType(CircularProgressIndicator), findsOneWidget); }); ``` ## Linting ### Run Riverpod Lint ```bash # Check for Riverpod-specific issues dart run custom_lint # Auto-fix issues dart run custom_lint --fix ``` ### Riverpod Lint Rules Enabled - `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 (don't use ref.read in build) - `avoid_manual_providers_as_generated_provider_dependency` - Use generated providers - `functional_ref` - Proper ref usage - `notifier_build` - Proper Notifier implementation ## Common Issues & Solutions ### Issue 1: Generated files not found **Solution:** ```bash dart run build_runner build --delete-conflicting-outputs ``` ### Issue 2: Provider not updating **Solution:** Check if you're using `ref.watch()` not `ref.read()` in build method. ### Issue 3: Memory leaks **Solution:** Use autoDispose (default) for providers that should clean up. Only use keepAlive for global state. ### Issue 4: Too many rebuilds **Solution:** Use `.select()` to watch specific fields instead of entire objects. ## Migration from Riverpod 2.x ### StateNotifierProvider → Notifier ```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++; } ``` ### Provider.family → Function 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); } ``` ## Examples Comprehensive examples are available in: - `/lib/core/providers/provider_examples.dart` - All Riverpod 3.0 patterns - `/lib/core/providers/connectivity_provider.dart` - Real-world connectivity monitoring ## 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](./lib/core/providers/provider_examples.dart) ## Next Steps 1. Run `flutter pub get` to install dependencies 2. Run `dart run build_runner watch -d` to start code generation 3. Create feature-specific providers in `lib/features/*/presentation/providers/` 4. Follow the patterns in `provider_examples.dart` 5. Use connectivity_provider as a reference for real-world implementation ## Support For questions or issues: 1. Check provider_examples.dart for patterns 2. Review the Riverpod documentation 3. Run custom_lint to catch common mistakes 4. Use ref.watch() in build methods, ref.read() in event handlers