runable
This commit is contained in:
474
lib/core/providers/provider_examples.dart
Normal file
474
lib/core/providers/provider_examples.dart
Normal file
@@ -0,0 +1,474 @@
|
||||
// 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<String> 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<String> 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<List<String>> 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<int> timer(Ref ref) {
|
||||
return Stream.periodic(
|
||||
const Duration(seconds: 1),
|
||||
(count) => count,
|
||||
);
|
||||
}
|
||||
|
||||
/// Stream provider with parameters
|
||||
@riverpod
|
||||
Stream<String> 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<UserProfile>
|
||||
/// - 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<UserProfileData> 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<void> 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<void> 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<void> 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<List<String>> build() {
|
||||
// Return the stream
|
||||
return Stream.periodic(
|
||||
const Duration(seconds: 1),
|
||||
(count) => ['Message $count'],
|
||||
);
|
||||
}
|
||||
|
||||
/// Send a new message
|
||||
Future<void> 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<String> 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<String> build() async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return 'Initial data';
|
||||
}
|
||||
|
||||
/// Refresh this provider's data
|
||||
Future<void> 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<String> build() async {
|
||||
return await _fetchData();
|
||||
}
|
||||
|
||||
Future<String> _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<void> 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();
|
||||
}
|
||||
|
||||
*/
|
||||
Reference in New Issue
Block a user