// ignore_for_file: unreachable_from_main import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'provider_examples.g.dart'; /// ============================================================================ /// RIVERPOD 3.0 PROVIDER EXAMPLES WITH CODE GENERATION /// ============================================================================ /// This file contains comprehensive examples of Riverpod 3.0 patterns /// using the @riverpod annotation and code generation. /// /// Key Changes in Riverpod 3.0: /// - Unified Ref type (no more FutureProviderRef, StreamProviderRef, etc.) /// - Simplified Notifier classes (no more separate AutoDisposeNotifier) /// - Automatic retry for failed providers /// - ref.mounted check after async operations /// - Improved family parameters (just function parameters!) /// ============================================================================ // ============================================================================ // 1. SIMPLE IMMUTABLE VALUE PROVIDER // ============================================================================ /// Simple provider that returns an immutable value /// Use this for constants, configurations, or computed values @riverpod String appVersion(Ref ref) { return '1.0.0'; } /// Provider with computation @riverpod int pointsMultiplier(Ref ref) { // Can read other providers final userTier = 'diamond'; // This would come from another provider return userTier == 'diamond' ? 3 : userTier == 'platinum' ? 2 : 1; } // ============================================================================ // 2. ASYNC DATA FETCHING (FutureProvider pattern) // ============================================================================ /// Async provider for fetching data once /// Automatically handles loading and error states via AsyncValue @riverpod Future userData(Ref ref) async { // Simulate API call await Future.delayed(const Duration(seconds: 1)); return 'User Data'; } /// Async provider with parameters (Family pattern) /// Parameters are just function parameters - much simpler than before! @riverpod Future userProfile(Ref ref, String userId) async { // Simulate API call with userId await Future.delayed(const Duration(seconds: 1)); return 'Profile for user: $userId'; } /// Async provider with multiple parameters /// Named parameters, optional parameters, defaults - all supported! @riverpod Future> productList( Ref ref, { required String category, int page = 1, int limit = 20, String? searchQuery, }) async { // Simulate API call with parameters await Future.delayed(const Duration(milliseconds: 500)); return ['Product 1', 'Product 2', 'Product 3']; } // ============================================================================ // 3. STREAM PROVIDER // ============================================================================ /// Stream provider for real-time data /// Use this for WebSocket connections, real-time updates, etc. @riverpod Stream timer(Ref ref) { return Stream.periodic( const Duration(seconds: 1), (count) => count, ); } /// Stream provider with parameters @riverpod Stream chatMessages(Ref ref, String roomId) { // Simulate WebSocket stream return Stream.periodic( const Duration(seconds: 2), (count) => 'Message $count in room $roomId', ); } // ============================================================================ // 4. NOTIFIER - MUTABLE STATE WITH METHODS // ============================================================================ /// Notifier for mutable state with methods /// Use this when you need to expose methods to modify state /// /// The @riverpod annotation generates: /// - counterProvider: Access the state /// - counterProvider.notifier: Access the notifier methods @riverpod class Counter extends _$Counter { /// Build method returns the initial state @override int build() { return 0; } /// Methods to modify state void increment() { state++; } void decrement() { state--; } void reset() { state = 0; } void add(int value) { state += value; } } /// Notifier with parameters (Family pattern) @riverpod class CartQuantity extends _$CartQuantity { /// Parameters become properties you can access @override int build(String productId) { // Initialize with 0 or load from local storage return 0; } void increment() { state++; } void decrement() { if (state > 0) { state--; } } void setQuantity(int quantity) { state = quantity.clamp(0, 99); } } // ============================================================================ // 5. ASYNC NOTIFIER - MUTABLE STATE WITH ASYNC INITIALIZATION // ============================================================================ /// AsyncNotifier for state that requires async initialization /// Perfect for fetching data that can then be modified /// /// State type: AsyncValue /// - AsyncValue.data(profile) when loaded /// - AsyncValue.loading() when loading /// - AsyncValue.error(error, stack) when error @riverpod class UserProfileNotifier extends _$UserProfileNotifier { /// Build method returns Future of the initial state @override Future build() async { // Fetch initial data await Future.delayed(const Duration(seconds: 1)); return UserProfileData(name: 'John Doe', email: 'john@example.com'); } /// Method to update profile /// Uses AsyncValue.guard() for proper error handling Future updateName(String name) async { // Set loading state state = const AsyncValue.loading(); // Update state with error handling state = await AsyncValue.guard(() async { await Future.delayed(const Duration(milliseconds: 500)); final currentProfile = await future; // Get current data return currentProfile.copyWith(name: name); }); } /// Refresh data Future refresh() async { state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { await Future.delayed(const Duration(seconds: 1)); return UserProfileData(name: 'Refreshed', email: 'refresh@example.com'); }); } /// Method with ref.mounted check (Riverpod 3.0 feature) Future updateWithMountedCheck(String name) async { await Future.delayed(const Duration(seconds: 2)); // Check if provider is still mounted after async operation if (!ref.mounted) return; state = AsyncValue.data( UserProfileData(name: name, email: 'email@example.com'), ); } } // Simple data class for example class UserProfileData { UserProfileData({required this.name, required this.email}); final String name; final String email; UserProfileData copyWith({String? name, String? email}) { return UserProfileData( name: name ?? this.name, email: email ?? this.email, ); } } // ============================================================================ // 6. STREAM NOTIFIER - MUTABLE STATE FROM STREAM // ============================================================================ /// StreamNotifier for state that comes from a stream but can be modified /// Use for WebSocket connections with additional actions @riverpod class LiveChatNotifier extends _$LiveChatNotifier { @override Stream> build() { // Return the stream return Stream.periodic( const Duration(seconds: 1), (count) => ['Message $count'], ); } /// Send a new message Future sendMessage(String message) async { // Send via WebSocket/API await Future.delayed(const Duration(milliseconds: 100)); } } // ============================================================================ // 7. PROVIDER DEPENDENCIES (COMPOSITION) // ============================================================================ /// Provider that depends on other providers @riverpod Future dashboardData(Ref ref) async { // Watch other providers final userData = await ref.watch(userDataProvider.future); final version = ref.watch(appVersionProvider); return 'Dashboard for $userData on version $version'; } /// Provider that selectively watches for optimization /// Note: userProfileProvider is actually a Family provider (takes userId parameter) /// In real code, you would use the generated AsyncNotifier provider @riverpod String userDisplayName(Ref ref) { // Example: Watch a simple computed value based on other providers final version = ref.watch(appVersionProvider); // In a real app, you would watch the UserProfileNotifier like: // final asyncProfile = ref.watch(userProfileNotifierProvider); // return asyncProfile.when(...) return 'User on version $version'; } // ============================================================================ // 8. KEEP ALIVE vs AUTO DISPOSE // ============================================================================ /// By default, generated providers are autoDispose /// They clean up when no longer used @riverpod String autoDisposeExample(Ref ref) { // This provider will be disposed when no longer watched ref.onDispose(() { // Clean up resources }); return 'Auto disposed'; } /// Keep alive provider - never auto-disposes /// Use for global state, singletons, services @Riverpod(keepAlive: true) String keepAliveExample(Ref ref) { // This provider stays alive until the app closes return 'Kept alive'; } // ============================================================================ // 9. LIFECYCLE HOOKS // ============================================================================ /// Provider with lifecycle hooks @riverpod String lifecycleExample(Ref ref) { // Called when provider is first created ref.onDispose(() { // Clean up when provider is disposed print('Provider disposed'); }); ref.onCancel(() { // Called when last listener is removed (before dispose) print('Last listener removed'); }); ref.onResume(() { // Called when a new listener is added after onCancel print('New listener added'); }); return 'Lifecycle example'; } // ============================================================================ // 10. INVALIDATION AND REFRESH // ============================================================================ /// Provider showing how to invalidate and refresh @riverpod class DataManager extends _$DataManager { @override Future build() async { await Future.delayed(const Duration(seconds: 1)); return 'Initial data'; } /// Refresh this provider's data Future refresh() async { // Method 1: Use ref.invalidateSelf() ref.invalidateSelf(); // Method 2: Manually update state state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { await Future.delayed(const Duration(seconds: 1)); return 'Refreshed data'; }); } /// Invalidate another provider void invalidateOther() { ref.invalidate(userDataProvider); } } // ============================================================================ // 11. ERROR HANDLING PATTERNS // ============================================================================ /// Provider with comprehensive error handling @riverpod class ErrorHandlingExample extends _$ErrorHandlingExample { @override Future build() async { return await _fetchData(); } Future _fetchData() async { try { // Simulate API call await Future.delayed(const Duration(seconds: 1)); return 'Success'; } catch (e) { // Errors are automatically caught by AsyncValue rethrow; } } /// Update with error handling Future updateData(String newData) async { state = const AsyncValue.loading(); // AsyncValue.guard automatically catches errors state = await AsyncValue.guard(() async { await Future.delayed(const Duration(milliseconds: 500)); return newData; }); // Handle the result state.when( data: (data) { // Success print('Updated successfully: $data'); }, loading: () { // Still loading }, error: (error, stack) { // Handle error print('Update failed: $error'); }, ); } } // ============================================================================ // USAGE IN WIDGETS // ============================================================================ /* // 1. Watching a simple provider final version = ref.watch(appVersionProvider); // 2. Watching async provider final userData = ref.watch(userDataProvider); userData.when( data: (data) => Text(data), loading: () => CircularProgressIndicator(), error: (error, stack) => Text('Error: $error'), ); // 3. Watching with family (parameters) final profile = ref.watch(userProfileProvider('user123')); // 4. Watching state from Notifier final count = ref.watch(counterProvider); // 5. Calling Notifier methods ref.read(counterProvider.notifier).increment(); // 6. Watching AsyncNotifier state final profileState = ref.watch(userProfileNotifierProvider); // 7. Calling AsyncNotifier methods await ref.read(userProfileNotifierProvider.notifier).updateName('New Name'); // 8. Selective watching for optimization final userName = ref.watch( userProfileNotifierProvider.select((async) => async.value?.name), ); // 9. Invalidating a provider ref.invalidate(userDataProvider); // 10. Refreshing a provider ref.refresh(userDataProvider); // 11. Pattern matching (Dart 3.0+) final profileState = ref.watch(userProfileNotifierProvider); switch (profileState) { case AsyncData(:final value): return Text(value.name); case AsyncError(:final error): return Text('Error: $error'); case AsyncLoading(): return CircularProgressIndicator(); } */