Riverpod State Management Setup
This directory contains a comprehensive Riverpod state management setup following Riverpod 2.x best practices with modern provider patterns.
📁 Directory Structure
lib/core/providers/
├── providers.dart # Barrel file - single import point
├── app_providers.dart # Global app state and initialization
├── theme_providers.dart # Theme and UI state management
├── storage_providers.dart # Secure storage and Hive management
├── network_providers.dart # HTTP clients and API providers
├── api_providers.dart # API-specific providers
└── provider_usage_example.dart # Usage examples and patterns
lib/shared/presentation/providers/
└── connectivity_providers.dart # Network connectivity monitoring
🚀 Key Features
Modern Riverpod 2.x Patterns
- AsyncNotifierProvider: For async mutable state management
- NotifierProvider: For synchronous mutable state management
- StreamProvider: For reactive data streams
- Provider: For dependency injection and immutable values
- Code Generation: Using
@riverpodannotation for type safety
Comprehensive State Management
- App Initialization: Multi-stage app startup with error handling
- Theme Management: Dark/light mode with system preference support
- Storage Integration: Hive + Secure Storage with health monitoring
- Network Connectivity: Real-time connection status and history
- Feature Flags: Dynamic feature toggling
- Error Tracking: Centralized error logging and monitoring
Performance Optimized
- State Persistence: Automatic state saving and restoration
- Efficient Rebuilds: Minimal widget rebuilds with proper selectors
- Resource Management: Automatic disposal and cleanup
- Caching Strategy: Intelligent data caching with expiration
📋 Provider Categories
🏗️ Core Application (app_providers.dart)
// App initialization with multi-stage loading
final initData = ref.watch(appInitializationProvider);
// Global app state management
final globalState = ref.watch(globalAppStateProvider);
// Feature flags for conditional features
final featureFlags = ref.watch(featureFlagsProvider);
🎨 Theme & UI (theme_providers.dart)
// Theme mode management with persistence
final themeMode = ref.watch(currentThemeModeProvider);
await ref.read(appSettingsNotifierProvider.notifier)
.updateThemeMode(AppThemeMode.dark);
// Reactive theme changes
final isDark = ref.watch(isDarkModeProvider);
💾 Storage Management (storage_providers.dart)
// Secure storage operations
final secureNotifier = ref.read(secureStorageNotifierProvider.notifier);
await secureNotifier.store('token', 'secure_value');
// Hive storage with health monitoring
final storageHealth = ref.watch(storageHealthMonitorProvider);
🌐 Network Connectivity (connectivity_providers.dart)
// Real-time connectivity monitoring
final isConnected = ref.watch(isConnectedProvider);
final connectionType = ref.watch(connectionTypeProvider);
// Network history and statistics
final networkHistory = ref.watch(networkHistoryNotifierProvider);
🔧 Usage Patterns
1. Basic Provider Consumption
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final settingsAsync = ref.watch(appSettingsNotifierProvider);
return settingsAsync.when(
data: (settings) => Text('Theme: ${settings.themeMode}'),
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Error: $error'),
);
}
}
2. State Mutation
// Theme change
await ref.read(appSettingsNotifierProvider.notifier)
.updateThemeMode(AppThemeMode.dark);
// Feature flag toggle
ref.read(featureFlagsProvider.notifier)
.toggleFeature('darkMode');
// Storage operations
await ref.read(secureStorageNotifierProvider.notifier)
.store('api_key', newApiKey);
3. Provider Listening
// Listen for state changes
ref.listen(networkStatusNotifierProvider, (previous, next) {
if (next.isConnected && !previous?.isConnected) {
// Connection restored
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Connection restored')),
);
}
});
4. Combining Providers
@riverpod
String userStatus(UserStatusRef ref) {
final isConnected = ref.watch(isConnectedProvider);
final settings = ref.watch(appSettingsNotifierProvider);
return settings.when(
data: (data) => isConnected
? 'Online - ${data.locale}'
: 'Offline - ${data.locale}',
loading: () => 'Loading...',
error: (_, __) => 'Error',
);
}
📱 App Integration
1. Provider Setup
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Hive before app starts
await HiveService.init();
runApp(
ProviderScope(
child: const MyApp(),
),
);
}
2. App Initialization
class MyApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final initAsync = ref.watch(appInitializationProvider);
return initAsync.when(
data: (data) => data.state == AppInitializationState.initialized
? const MainApp()
: const SplashScreen(),
loading: () => const SplashScreen(),
error: (error, stack) => ErrorApp(error: error),
);
}
}
3. Theme Integration
class MainApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(effectiveThemeModeProvider);
return MaterialApp(
themeMode: themeMode,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: const HomeScreen(),
);
}
}
🔄 State Persistence
Automatic Persistence
- Theme Mode: Automatically saved to Hive
- User Preferences: Persisted across app restarts
- Feature Flags: Maintained in local storage
- Network History: Cached for analytics
Manual Persistence
// Save custom app state
await ref.read(appSettingsNotifierProvider.notifier)
.setCustomSetting('user_preference', value);
// Restore state on app start
final customValue = settings.getCustomSetting<String>('user_preference');
🚨 Error Handling
Provider-Level Error Handling
@riverpod
class DataNotifier extends _$DataNotifier {
@override
Future<Data> build() async {
try {
return await loadData();
} catch (error, stackTrace) {
// Log error for debugging
ref.read(errorTrackerProvider.notifier)
.logError(error, stackTrace);
rethrow;
}
}
}
Global Error Tracking
// Access error history
final recentErrors = ref.read(errorTrackerProvider.notifier)
.getRecentErrors(count: 5);
// Clear error history
ref.read(errorTrackerProvider.notifier).clearErrors();
🔍 Debugging & Monitoring
Provider Inspector
// Check provider state in debug mode
if (kDebugMode) {
final globalState = ref.read(globalAppStateProvider);
debugPrint('App State: $globalState');
}
Storage Health Monitoring
// Perform health check
await ref.read(storageHealthMonitorProvider.notifier)
.performHealthCheck();
// Check storage statistics
final stats = ref.read(hiveStorageNotifierProvider.notifier)
.getStorageStats();
🛠️ Best Practices
- Import: Use the barrel file
import '../core/providers/providers.dart' - Error Handling: Always handle AsyncValue error states
- Disposal: Providers auto-dispose, but manual cleanup when needed
- Performance: Use
select()for specific state slices - Testing: Mock providers using
ProviderContainer - State Size: Keep provider state minimal and focused
- Provider Names: Use descriptive names indicating purpose
- Documentation: Document complex provider logic
📚 Code Generation
The providers use Riverpod's code generation. Run this command after making changes:
dart run build_runner build
For continuous generation during development:
dart run build_runner watch
🧪 Testing
Provider Testing
test('theme provider updates correctly', () async {
final container = ProviderContainer();
final notifier = container.read(appSettingsNotifierProvider.notifier);
await notifier.updateThemeMode(AppThemeMode.dark);
final settings = container.read(appSettingsNotifierProvider);
expect(settings.value?.themeMode, 'dark');
container.dispose();
});
Widget Testing with Providers
testWidgets('widget shows correct theme', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
home: MyWidget(),
),
),
);
expect(find.text('Theme: system'), findsOneWidget);
});
📄 Dependencies
This setup requires the following dependencies in pubspec.yaml:
dependencies:
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
connectivity_plus: ^6.0.5
flutter_secure_storage: ^9.2.2
hive: ^2.2.3
hive_flutter: ^1.1.0
dev_dependencies:
riverpod_generator: ^2.4.0
build_runner: ^2.4.7
🔄 Migration from Provider/Bloc
If migrating from other state management solutions:
- Replace Provider with Riverpod providers
- Convert Bloc to AsyncNotifierProvider
- Update UI to use ConsumerWidget
- Migrate state persistence logic
- Update testing to use ProviderContainer
This setup provides a solid foundation for scalable Flutter applications with proper state management, error handling, and performance optimization.