Files
worker/lib/core/providers/provider_examples.dart
Phuoc Nguyen 628c81ce13 runable
2025-10-17 17:22:28 +07:00

475 lines
14 KiB
Dart

// 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();
}
*/