This commit is contained in:
2025-09-26 18:48:14 +07:00
parent 382a0e7909
commit 30ed6b39b5
85 changed files with 20722 additions and 112 deletions

View File

@@ -0,0 +1,360 @@
# 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 `@riverpod` annotation 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`)
```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`)
```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`)
```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`)
```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
```dart
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
```dart
// 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
```dart
// 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
```dart
@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
```dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Hive before app starts
await HiveService.init();
runApp(
ProviderScope(
child: const MyApp(),
),
);
}
```
### 2. App Initialization
```dart
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
```dart
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
```dart
// 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
```dart
@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
```dart
// Access error history
final recentErrors = ref.read(errorTrackerProvider.notifier)
.getRecentErrors(count: 5);
// Clear error history
ref.read(errorTrackerProvider.notifier).clearErrors();
```
## 🔍 Debugging & Monitoring
### Provider Inspector
```dart
// Check provider state in debug mode
if (kDebugMode) {
final globalState = ref.read(globalAppStateProvider);
debugPrint('App State: $globalState');
}
```
### Storage Health Monitoring
```dart
// Perform health check
await ref.read(storageHealthMonitorProvider.notifier)
.performHealthCheck();
// Check storage statistics
final stats = ref.read(hiveStorageNotifierProvider.notifier)
.getStorageStats();
```
## 🛠️ Best Practices
1. **Import**: Use the barrel file `import '../core/providers/providers.dart'`
2. **Error Handling**: Always handle AsyncValue error states
3. **Disposal**: Providers auto-dispose, but manual cleanup when needed
4. **Performance**: Use `select()` for specific state slices
5. **Testing**: Mock providers using `ProviderContainer`
6. **State Size**: Keep provider state minimal and focused
7. **Provider Names**: Use descriptive names indicating purpose
8. **Documentation**: Document complex provider logic
## 📚 Code Generation
The providers use Riverpod's code generation. Run this command after making changes:
```bash
dart run build_runner build
```
For continuous generation during development:
```bash
dart run build_runner watch
```
## 🧪 Testing
### Provider Testing
```dart
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
```dart
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`:
```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:
1. Replace Provider with Riverpod providers
2. Convert Bloc to AsyncNotifierProvider
3. Update UI to use ConsumerWidget
4. Migrate state persistence logic
5. Update testing to use ProviderContainer
This setup provides a solid foundation for scalable Flutter applications with proper state management, error handling, and performance optimization.

View File

@@ -0,0 +1,34 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../services/api_service.dart';
import 'network_providers.dart';
/// Provider for ExampleApiService
final exampleApiServiceProvider = Provider<ExampleApiService>((ref) {
final dioClient = ref.watch(dioClientProvider);
return ExampleApiService(dioClient);
});
/// Provider for AuthApiService
final authApiServiceProvider = Provider<AuthApiService>((ref) {
final dioClient = ref.watch(dioClientProvider);
return AuthApiService(dioClient);
});
/// Provider to check authentication status
final isAuthenticatedProvider = FutureProvider<bool>((ref) async {
final authService = ref.watch(authApiServiceProvider);
return await authService.isAuthenticated();
});
/// Example provider for user profile data
final userProfileProvider = FutureProvider.family<Map<String, dynamic>, String>((ref, userId) async {
final apiService = ref.watch(exampleApiServiceProvider);
return await apiService.getUserProfile(userId);
});
/// Example provider for posts list with pagination
final postsProvider = FutureProvider.family<List<Map<String, dynamic>>, ({int page, int limit})>((ref, params) async {
final apiService = ref.watch(exampleApiServiceProvider);
return await apiService.getPosts(page: params.page, limit: params.limit);
});

View File

@@ -0,0 +1,348 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter/foundation.dart';
import '../database/hive_service.dart';
import '../database/models/app_settings.dart';
import '../database/providers/database_providers.dart';
import '../database/repositories/settings_repository.dart';
import '../database/repositories/cache_repository.dart';
import '../database/repositories/user_preferences_repository.dart';
part 'app_providers.g.dart';
/// App initialization state
enum AppInitializationState {
uninitialized,
initializing,
initialized,
error,
}
/// App initialization data
class AppInitializationData {
final AppInitializationState state;
final String? error;
final DateTime? initializedAt;
const AppInitializationData({
required this.state,
this.error,
this.initializedAt,
});
AppInitializationData copyWith({
AppInitializationState? state,
String? error,
DateTime? initializedAt,
}) {
return AppInitializationData(
state: state ?? this.state,
error: error ?? this.error,
initializedAt: initializedAt ?? this.initializedAt,
);
}
}
/// Repository providers
@riverpod
CacheRepository cacheRepository(CacheRepositoryRef ref) {
return CacheRepository();
}
@riverpod
UserPreferencesRepository userPreferencesRepository(UserPreferencesRepositoryRef ref) {
return UserPreferencesRepository();
}
/// App initialization provider
@riverpod
class AppInitialization extends _$AppInitialization {
@override
Future<AppInitializationData> build() async {
return _initializeApp();
}
Future<AppInitializationData> _initializeApp() async {
try {
debugPrint('🚀 Starting app initialization...');
// Initialize Hive
debugPrint('📦 Initializing Hive database...');
await HiveService.initialize();
// Initialize repositories
debugPrint('🗂️ Initializing repositories...');
final settingsRepo = ref.read(settingsRepositoryProvider);
final cacheRepo = ref.read(cacheRepositoryProvider);
final userPrefsRepo = ref.read(userPreferencesRepositoryProvider);
// Load initial settings
debugPrint('⚙️ Loading app settings...');
final settings = settingsRepo.getSettings();
// Initialize user preferences if needed
debugPrint('👤 Initializing user preferences...');
userPrefsRepo.getPreferences();
// Perform any necessary migrations
debugPrint('🔄 Checking for migrations...');
await _performMigrations(settingsRepo, settings);
// Clean up expired cache entries
debugPrint('🧹 Cleaning up expired cache...');
await cacheRepo.cleanExpiredItems();
debugPrint('✅ App initialization completed successfully');
return AppInitializationData(
state: AppInitializationState.initialized,
initializedAt: DateTime.now(),
);
} catch (error, stackTrace) {
debugPrint('❌ App initialization failed: $error');
debugPrint('Stack trace: $stackTrace');
return AppInitializationData(
state: AppInitializationState.error,
error: error.toString(),
);
}
}
Future<void> _performMigrations(SettingsRepository repo, AppSettings settings) async {
// Add any migration logic here
if (settings.version < 1) {
debugPrint('🔄 Migrating settings to version 1...');
// Migration logic would go here
}
}
/// Retry initialization
Future<void> retry() async {
state = const AsyncValue.loading();
state = AsyncValue.data(await _initializeApp());
}
/// Force re-initialization
Future<void> reinitialize() async {
state = const AsyncValue.loading();
try {
// Close all Hive boxes
await HiveService.closeAll();
// Re-initialize everything
state = AsyncValue.data(await _initializeApp());
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
}
}
}
/// App version provider
@riverpod
String appVersion(AppVersionRef ref) {
// This would typically come from package_info_plus
return '1.0.0+1';
}
/// App build mode provider
@riverpod
String appBuildMode(AppBuildModeRef ref) {
if (kDebugMode) return 'debug';
if (kProfileMode) return 'profile';
return 'release';
}
/// App ready state provider
@riverpod
bool isAppReady(IsAppReadyRef ref) {
final initData = ref.watch(appInitializationProvider);
return initData.when(
data: (data) => data.state == AppInitializationState.initialized,
loading: () => false,
error: (_, __) => false,
);
}
/// Global app state notifier
@riverpod
class GlobalAppState extends _$GlobalAppState {
@override
Map<String, dynamic> build() {
final initAsync = ref.watch(appInitializationProvider);
final appVersion = ref.watch(appVersionProvider);
final buildMode = ref.watch(appBuildModeProvider);
return initAsync.when(
data: (initData) => {
'isInitialized': initData.state == AppInitializationState.initialized,
'initializationState': initData.state,
'initializationError': initData.error,
'initializedAt': initData.initializedAt?.toIso8601String(),
'appVersion': appVersion,
'buildMode': buildMode,
'isReady': initData.state == AppInitializationState.initialized,
},
loading: () => {
'isInitialized': false,
'initializationState': AppInitializationState.initializing,
'initializationError': null,
'initializedAt': null,
'appVersion': appVersion,
'buildMode': buildMode,
'isReady': false,
},
error: (error, _) => {
'isInitialized': false,
'initializationState': AppInitializationState.error,
'initializationError': error.toString(),
'initializedAt': null,
'appVersion': appVersion,
'buildMode': buildMode,
'isReady': false,
},
);
}
/// Update global state
void updateState(String key, dynamic value) {
final currentState = Map<String, dynamic>.from(state);
currentState[key] = value;
state = currentState;
}
/// Reset global state
void reset() {
ref.invalidate(appInitializationProvider);
}
}
/// Feature flags provider
@riverpod
class FeatureFlags extends _$FeatureFlags {
@override
Map<String, bool> build() {
final buildMode = ref.watch(appBuildModeProvider);
return {
'enableDebugMode': buildMode == 'debug',
'enableAnalytics': buildMode == 'release',
'enableCrashReporting': buildMode != 'debug',
'enablePerformanceMonitoring': true,
'enableOfflineMode': true,
'enableDarkMode': true,
'enableNotifications': true,
};
}
/// Check if feature is enabled
bool isEnabled(String feature) {
return state[feature] ?? false;
}
/// Enable feature
void enableFeature(String feature) {
state = {...state, feature: true};
}
/// Disable feature
void disableFeature(String feature) {
state = {...state, feature: false};
}
/// Toggle feature
void toggleFeature(String feature) {
final currentValue = state[feature] ?? false;
state = {...state, feature: !currentValue};
}
}
/// App configuration provider
@riverpod
class AppConfiguration extends _$AppConfiguration {
@override
Map<String, dynamic> build() {
final buildMode = ref.watch(appBuildModeProvider);
return {
'apiTimeout': 30000, // 30 seconds
'cacheTimeout': 3600000, // 1 hour in milliseconds
'maxRetries': 3,
'retryDelay': 1000, // 1 second
'enableLogging': buildMode == 'debug',
'logLevel': buildMode == 'debug' ? 'verbose' : 'error',
'maxCacheSize': 100 * 1024 * 1024, // 100MB
'imageQuality': 85,
'compressionEnabled': true,
};
}
/// Get configuration value
T? getValue<T>(String key) {
return state[key] as T?;
}
/// Update configuration
void updateConfiguration(String key, dynamic value) {
state = {...state, key: value};
}
/// Update multiple configurations
void updateConfigurations(Map<String, dynamic> updates) {
state = {...state, ...updates};
}
}
/// App lifecycle state provider
@riverpod
class AppLifecycleNotifier extends _$AppLifecycleNotifier {
@override
String build() {
return 'resumed'; // Default state
}
void updateState(String newState) {
state = newState;
debugPrint('📱 App lifecycle state changed to: $newState');
}
}
/// Error tracking provider
@riverpod
class ErrorTracker extends _$ErrorTracker {
@override
List<Map<String, dynamic>> build() {
return [];
}
/// Log error
void logError(dynamic error, StackTrace? stackTrace, {Map<String, dynamic>? context}) {
final errorEntry = {
'error': error.toString(),
'stackTrace': stackTrace?.toString(),
'timestamp': DateTime.now().toIso8601String(),
'context': context,
};
state = [...state, errorEntry];
// Keep only last 100 errors to prevent memory issues
if (state.length > 100) {
state = state.sublist(state.length - 100);
}
debugPrint('🐛 Error logged: $error');
}
/// Clear errors
void clearErrors() {
state = [];
}
/// Get recent errors
List<Map<String, dynamic>> getRecentErrors({int count = 10}) {
return state.reversed.take(count).toList();
}
}

View File

@@ -0,0 +1,200 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'app_providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$cacheRepositoryHash() => r'0137087454bd51e0465886de0eab7acdc124ecb9';
/// Repository providers
///
/// Copied from [cacheRepository].
@ProviderFor(cacheRepository)
final cacheRepositoryProvider = AutoDisposeProvider<CacheRepository>.internal(
cacheRepository,
name: r'cacheRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$cacheRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef CacheRepositoryRef = AutoDisposeProviderRef<CacheRepository>;
String _$userPreferencesRepositoryHash() =>
r'0244be191fd7576cbfc90468fe491306ed06d537';
/// See also [userPreferencesRepository].
@ProviderFor(userPreferencesRepository)
final userPreferencesRepositoryProvider =
AutoDisposeProvider<UserPreferencesRepository>.internal(
userPreferencesRepository,
name: r'userPreferencesRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$userPreferencesRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef UserPreferencesRepositoryRef
= AutoDisposeProviderRef<UserPreferencesRepository>;
String _$appVersionHash() => r'2605d9c0fd6d6a24e56caceadbe25b8370fedc4f';
/// App version provider
///
/// Copied from [appVersion].
@ProviderFor(appVersion)
final appVersionProvider = AutoDisposeProvider<String>.internal(
appVersion,
name: r'appVersionProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$appVersionHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef AppVersionRef = AutoDisposeProviderRef<String>;
String _$appBuildModeHash() => r'fa100842dc5c894edb352036f8d887d97618f696';
/// App build mode provider
///
/// Copied from [appBuildMode].
@ProviderFor(appBuildMode)
final appBuildModeProvider = AutoDisposeProvider<String>.internal(
appBuildMode,
name: r'appBuildModeProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$appBuildModeHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef AppBuildModeRef = AutoDisposeProviderRef<String>;
String _$isAppReadyHash() => r'b23a0450aa7bb2c9e3ea07630429118f239e610a';
/// App ready state provider
///
/// Copied from [isAppReady].
@ProviderFor(isAppReady)
final isAppReadyProvider = AutoDisposeProvider<bool>.internal(
isAppReady,
name: r'isAppReadyProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$isAppReadyHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef IsAppReadyRef = AutoDisposeProviderRef<bool>;
String _$appInitializationHash() => r'eb87040a5ee3d20a172bef9221c2c56d7e07fe77';
/// App initialization provider
///
/// Copied from [AppInitialization].
@ProviderFor(AppInitialization)
final appInitializationProvider = AutoDisposeAsyncNotifierProvider<
AppInitialization, AppInitializationData>.internal(
AppInitialization.new,
name: r'appInitializationProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$appInitializationHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AppInitialization = AutoDisposeAsyncNotifier<AppInitializationData>;
String _$globalAppStateHash() => r'fd0daa69a2a1dc4aaa3af95a1b148ba1e6de0e3f';
/// Global app state notifier
///
/// Copied from [GlobalAppState].
@ProviderFor(GlobalAppState)
final globalAppStateProvider =
AutoDisposeNotifierProvider<GlobalAppState, Map<String, dynamic>>.internal(
GlobalAppState.new,
name: r'globalAppStateProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$globalAppStateHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$GlobalAppState = AutoDisposeNotifier<Map<String, dynamic>>;
String _$featureFlagsHash() => r'747e9d64c73eed5b374f37a8f28eb4b7fc94e53d';
/// Feature flags provider
///
/// Copied from [FeatureFlags].
@ProviderFor(FeatureFlags)
final featureFlagsProvider =
AutoDisposeNotifierProvider<FeatureFlags, Map<String, bool>>.internal(
FeatureFlags.new,
name: r'featureFlagsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$featureFlagsHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$FeatureFlags = AutoDisposeNotifier<Map<String, bool>>;
String _$appConfigurationHash() => r'115fff1ac67a37ff620bbd15ea142a7211e9dc9c';
/// App configuration provider
///
/// Copied from [AppConfiguration].
@ProviderFor(AppConfiguration)
final appConfigurationProvider = AutoDisposeNotifierProvider<AppConfiguration,
Map<String, dynamic>>.internal(
AppConfiguration.new,
name: r'appConfigurationProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$appConfigurationHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AppConfiguration = AutoDisposeNotifier<Map<String, dynamic>>;
String _$appLifecycleNotifierHash() =>
r'344a33715910c38bccc596ac0b543e59cb5752a0';
/// App lifecycle state provider
///
/// Copied from [AppLifecycleNotifier].
@ProviderFor(AppLifecycleNotifier)
final appLifecycleNotifierProvider =
AutoDisposeNotifierProvider<AppLifecycleNotifier, String>.internal(
AppLifecycleNotifier.new,
name: r'appLifecycleNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$appLifecycleNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AppLifecycleNotifier = AutoDisposeNotifier<String>;
String _$errorTrackerHash() => r'c286897f0ac33b2b619be30d3fd8d18331635b88';
/// Error tracking provider
///
/// Copied from [ErrorTracker].
@ProviderFor(ErrorTracker)
final errorTrackerProvider = AutoDisposeNotifierProvider<ErrorTracker,
List<Map<String, dynamic>>>.internal(
ErrorTracker.new,
name: r'errorTrackerProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$errorTrackerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ErrorTracker = AutoDisposeNotifier<List<Map<String, dynamic>>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@@ -0,0 +1,51 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../network/dio_client.dart';
import '../network/network_info.dart';
/// Provider for Connectivity instance
final connectivityProvider = Provider<Connectivity>((ref) {
return Connectivity();
});
/// Provider for FlutterSecureStorage instance
final secureStorageProvider = Provider<FlutterSecureStorage>((ref) {
return const FlutterSecureStorage();
});
/// Provider for NetworkInfo implementation
final networkInfoProvider = Provider<NetworkInfo>((ref) {
final connectivity = ref.watch(connectivityProvider);
return NetworkInfoImpl(connectivity);
});
/// Provider for DioClient - the main HTTP client
final dioClientProvider = Provider<DioClient>((ref) {
final networkInfo = ref.watch(networkInfoProvider);
final secureStorage = ref.watch(secureStorageProvider);
return DioClient(
networkInfo: networkInfo,
secureStorage: secureStorage,
);
});
/// Provider for network connectivity stream
final networkConnectivityProvider = StreamProvider<bool>((ref) {
final networkInfo = ref.watch(networkInfoProvider);
return networkInfo.connectionStream;
});
/// Provider for current network status
final isConnectedProvider = FutureProvider<bool>((ref) async {
final networkInfo = ref.watch(networkInfoProvider);
return await networkInfo.isConnected;
});
/// Utility provider to get detailed network connection information
final networkConnectionDetailsProvider = FutureProvider((ref) async {
final networkInfo = ref.watch(networkInfoProvider) as NetworkInfoImpl;
return await networkInfo.getConnectionDetails();
});

View File

@@ -0,0 +1,381 @@
/// Example usage of the Riverpod state management system
///
/// This file demonstrates how to properly use the various providers
/// created in this comprehensive Riverpod setup following 2.x best practices.
library;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers.dart'; // Import the barrel file
/// Example widget showing theme provider usage
class ThemeExampleWidget extends ConsumerWidget {
const ThemeExampleWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch theme-related providers
final settingsAsync = ref.watch(appSettingsNotifierProvider);
final currentTheme = ref.watch(currentThemeModeProvider);
final isDark = ref.watch(isDarkModeProvider);
return settingsAsync.when(
data: (settings) => Card(
child: Column(
children: [
ListTile(
title: const Text('Current Theme'),
subtitle: Text(currentTheme.value),
trailing: Switch(
value: isDark,
onChanged: (value) {
// Update theme mode
final notifier = ref.read(appSettingsNotifierProvider.notifier);
notifier.updateThemeMode(
value ? AppThemeMode.dark : AppThemeMode.light,
);
},
),
),
ListTile(
title: const Text('Notifications'),
trailing: Switch(
value: settings.notificationsEnabled,
onChanged: (value) {
final notifier = ref.read(appSettingsNotifierProvider.notifier);
notifier.updateNotificationsEnabled(value);
},
),
),
],
),
),
loading: () => const Card(
child: Center(child: CircularProgressIndicator()),
),
error: (error, stack) => Card(
child: ListTile(
title: const Text('Error loading settings'),
subtitle: Text(error.toString()),
leading: const Icon(Icons.error),
),
),
);
}
}
/// Example widget showing connectivity provider usage
class ConnectivityExampleWidget extends ConsumerWidget {
const ConnectivityExampleWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch connectivity providers
final isConnected = ref.watch(isConnectedProvider);
final connectionType = ref.watch(connectionTypeProvider);
final networkQuality = ref.watch(networkQualityProvider);
final isWifi = ref.watch(isWifiConnectedProvider);
return Card(
color: isConnected == true ? Colors.green.shade50 : Colors.red.shade50,
child: Column(
children: [
ListTile(
leading: Icon(
isConnected == true ? Icons.wifi : Icons.wifi_off,
color: isConnected == true ? Colors.green : Colors.red,
),
title: Text(isConnected == true ? 'Connected' : 'Disconnected'),
subtitle: Text('Connection: ${connectionType.toString().split('.').last}'),
),
if (isConnected == true) ...[
ListTile(
title: const Text('Network Quality'),
subtitle: Text(networkQuality),
),
if (isWifi == true)
const ListTile(
leading: Icon(Icons.wifi, color: Colors.blue),
title: Text('Using Wi-Fi'),
),
],
ListTile(
title: const Text('Refresh Network Status'),
trailing: IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
ref.read(networkStatusNotifierProvider.notifier).refresh();
},
),
),
],
),
);
}
}
/// Example widget showing app initialization and global state
class AppStateExampleWidget extends ConsumerWidget {
const AppStateExampleWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch app state providers
final initAsync = ref.watch(appInitializationProvider);
final globalState = ref.watch(globalAppStateProvider);
final isReady = ref.watch(isAppReadyProvider);
return Card(
child: Column(
children: [
ListTile(
title: const Text('App Status'),
subtitle: Text(isReady == true ? 'Ready' : 'Initializing...'),
leading: Icon(
isReady == true ? Icons.check_circle : Icons.hourglass_empty,
color: isReady == true ? Colors.green : Colors.orange,
),
),
ListTile(
title: const Text('App Version'),
subtitle: Text(globalState['appVersion'] ?? 'Unknown'),
),
ListTile(
title: const Text('Build Mode'),
subtitle: Text(globalState['buildMode'] ?? 'Unknown'),
),
initAsync.when(
data: (data) => ListTile(
title: const Text('Initialization Status'),
subtitle: Text(data.state.toString().split('.').last),
trailing: data.state == AppInitializationState.error
? IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
ref.read(appInitializationProvider.notifier).retry();
},
)
: null,
),
loading: () => const ListTile(
title: Text('Initialization Status'),
subtitle: Text('Loading...'),
trailing: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
error: (error, stack) => ListTile(
title: const Text('Initialization Error'),
subtitle: Text(error.toString()),
leading: const Icon(Icons.error, color: Colors.red),
),
),
],
),
);
}
}
/// Example widget showing storage provider usage
class StorageExampleWidget extends ConsumerWidget {
const StorageExampleWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch storage providers
final storageManager = ref.watch(storageManagerProvider);
final hiveStats = storageManager['hive'] as Map<String, dynamic>? ?? {};
final secureStorageInfo = storageManager['secureStorage'] as Map<String, dynamic>? ?? {};
final healthInfo = storageManager['health'] as Map<String, dynamic>? ?? {};
return Card(
child: Column(
children: [
ListTile(
title: const Text('Storage Health'),
subtitle: Text(healthInfo['isHealthy'] == true ? 'Healthy' : 'Issues detected'),
leading: Icon(
healthInfo['isHealthy'] == true ? Icons.check_circle : Icons.warning,
color: healthInfo['isHealthy'] == true ? Colors.green : Colors.orange,
),
),
ListTile(
title: const Text('Hive Storage'),
subtitle: Text('${hiveStats['appSettingsCount'] ?? 0} settings, ${hiveStats['cacheItemsCount'] ?? 0} cache items'),
),
ListTile(
title: const Text('Secure Storage'),
subtitle: Text('${secureStorageInfo['keyCount'] ?? 0} secure keys stored'),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () {
ref.read(storageManagerProvider.notifier).performMaintenance();
},
child: const Text('Maintenance'),
),
TextButton(
onPressed: () {
ref.read(storageHealthMonitorProvider.notifier).performHealthCheck();
},
child: const Text('Health Check'),
),
],
),
],
),
);
}
}
/// Example widget showing feature flags usage
class FeatureFlagsExampleWidget extends ConsumerWidget {
const FeatureFlagsExampleWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final featureFlags = ref.watch(featureFlagsProvider);
final flagsNotifier = ref.watch(featureFlagsProvider.notifier);
return Card(
child: Column(
children: [
const ListTile(
title: Text('Feature Flags'),
leading: Icon(Icons.flag),
),
...featureFlags.entries.map((entry) => ListTile(
title: Text(entry.key),
trailing: Switch(
value: entry.value,
onChanged: (value) {
if (value) {
flagsNotifier.enableFeature(entry.key);
} else {
flagsNotifier.disableFeature(entry.key);
}
},
),
)),
],
),
);
}
}
/// Example screen demonstrating all provider usage
class ProviderDemoScreen extends ConsumerWidget {
const ProviderDemoScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('Provider Demo'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
// Refresh all providers
ref.invalidate(appSettingsNotifierProvider);
ref.invalidate(networkStatusNotifierProvider);
ref.read(globalAppStateProvider.notifier).reset();
},
),
],
),
body: const SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
children: [
AppStateExampleWidget(),
SizedBox(height: 16),
ThemeExampleWidget(),
SizedBox(height: 16),
ConnectivityExampleWidget(),
SizedBox(height: 16),
StorageExampleWidget(),
SizedBox(height: 16),
FeatureFlagsExampleWidget(),
],
),
),
);
}
}
/// Example of using providers in a lifecycle-aware way
class ProviderLifecycleExample extends ConsumerStatefulWidget {
const ProviderLifecycleExample({super.key});
@override
ConsumerState<ProviderLifecycleExample> createState() => _ProviderLifecycleExampleState();
}
class _ProviderLifecycleExampleState extends ConsumerState<ProviderLifecycleExample>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
// Listen to app initialization
ref.listenManual(appInitializationProvider, (previous, next) {
next.when(
data: (data) {
if (data.state == AppInitializationState.initialized) {
debugPrint('✅ App initialized successfully');
}
},
loading: () => debugPrint('🔄 App initializing...'),
error: (error, stack) => debugPrint('❌ App initialization failed: $error'),
);
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
// Update app lifecycle state provider
ref.read(appLifecycleNotifierProvider.notifier).updateState(state.name);
// Handle different lifecycle states
switch (state) {
case AppLifecycleState.paused:
// App is paused, save important state
ref.read(storageManagerProvider.notifier).performMaintenance();
break;
case AppLifecycleState.resumed:
// App is resumed, refresh network status
ref.read(networkStatusNotifierProvider.notifier).refresh();
break;
case AppLifecycleState.detached:
// App is detached, cleanup if needed
break;
case AppLifecycleState.inactive:
case AppLifecycleState.hidden:
// Handle inactive/hidden states
break;
}
}
@override
Widget build(BuildContext context) {
// This would be your main app content
return const ProviderDemoScreen();
}
}

View File

@@ -0,0 +1,267 @@
/// Barrel file for all providers - central export point for dependency injection
///
/// This file exports all provider files to provide a single import point
/// for accessing all application providers following clean architecture principles.
///
/// Usage:
/// ```dart
/// import '../core/providers/providers.dart';
///
/// // Access any provider from the exported modules
/// final theme = ref.watch(currentThemeModeProvider);
/// final isConnected = ref.watch(isConnectedProvider);
/// ```
// Core application providers
export 'app_providers.dart';
// Network and API providers
export 'network_providers.dart';
export 'api_providers.dart';
// Theme and UI state providers
export 'theme_providers.dart';
// Storage and persistence providers
export 'storage_providers.dart' hide secureStorageProvider;
// Shared connectivity providers
export '../../../shared/presentation/providers/connectivity_providers.dart' hide connectivityProvider, isConnectedProvider;
/// Provider initialization helper
///
/// This class provides utilities for initializing and managing providers
/// across the application lifecycle.
class ProviderInitializer {
const ProviderInitializer._();
/// List of providers that need to be initialized early in the app lifecycle
/// These providers will be warmed up during app initialization to ensure
/// they're ready when needed.
static const List<String> criticalProviders = [
'appInitializationProvider',
'appSettingsNotifierProvider',
'networkStatusNotifierProvider',
'secureStorageNotifierProvider',
];
/// List of providers that can be initialized lazily when first accessed
static const List<String> lazyProviders = [
'featureFlagsProvider',
'appConfigurationProvider',
'networkHistoryNotifierProvider',
'errorTrackerProvider',
];
/// Provider categories for better organization and management
static const Map<String, List<String>> providerCategories = {
'core': [
'appInitializationProvider',
'globalAppStateProvider',
'appVersionProvider',
'appBuildModeProvider',
],
'theme': [
'appSettingsNotifierProvider',
'currentThemeModeProvider',
'effectiveThemeModeProvider',
'isDarkModeProvider',
'currentLocaleProvider',
],
'storage': [
'secureStorageNotifierProvider',
'hiveStorageNotifierProvider',
'storageHealthMonitorProvider',
'storageManagerProvider',
],
'network': [
'networkStatusNotifierProvider',
'networkConnectivityStreamProvider',
'isConnectedProvider',
'connectionTypeProvider',
'networkQualityProvider',
],
'utilities': [
'featureFlagsProvider',
'appConfigurationProvider',
'errorTrackerProvider',
'appLifecycleNotifierProvider',
],
};
/// Get providers by category
static List<String> getProvidersByCategory(String category) {
return providerCategories[category] ?? [];
}
/// Get all provider names
static List<String> getAllProviders() {
return providerCategories.values.expand((providers) => providers).toList();
}
/// Check if provider is critical (needs early initialization)
static bool isCriticalProvider(String providerName) {
return criticalProviders.contains(providerName);
}
/// Check if provider can be loaded lazily
static bool isLazyProvider(String providerName) {
return lazyProviders.contains(providerName);
}
}
/// Provider documentation and usage guidelines
///
/// This class contains documentation for proper provider usage patterns
/// following Riverpod 2.x best practices.
class ProviderDocumentation {
const ProviderDocumentation._();
/// Usage examples for different provider types
static const Map<String, String> usageExamples = {
'AsyncNotifierProvider': '''
// For async mutable state management
final notifier = ref.watch(appSettingsNotifierProvider.notifier);
await notifier.updateThemeMode(AppThemeMode.dark);
// Watch for state changes
ref.listen(appSettingsNotifierProvider, (previous, next) {
next.when(
data: (settings) => print('Settings updated'),
loading: () => print('Loading...'),
error: (error, stack) => print('Error: \$error'),
);
});
''',
'NotifierProvider': '''
// For synchronous mutable state
final flags = ref.watch(featureFlagsProvider);
final notifier = ref.watch(featureFlagsProvider.notifier);
// Update state
notifier.enableFeature('darkMode');
notifier.toggleFeature('analytics');
''',
'StreamProvider': '''
// For reactive data streams
final connectivityStream = ref.watch(networkConnectivityStreamProvider);
connectivityStream.when(
data: (status) => Text('Connected: \${status.isConnected}'),
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Error: \$error'),
);
''',
'Provider': '''
// For dependency injection and immutable values
final storage = ref.watch(secureStorageProvider);
final connectivity = ref.watch(connectivityProvider);
// Use in other providers
@riverpod
MyService myService(MyServiceRef ref) {
final storage = ref.watch(secureStorageProvider);
return MyService(storage);
}
''',
'FutureProvider': '''
// For async operations (read-only)
final initData = ref.watch(appInitializationProvider);
initData.when(
data: (data) => data.state == AppInitializationState.initialized
? const HomeScreen()
: const LoadingScreen(),
loading: () => const SplashScreen(),
error: (error, stack) => ErrorScreen(error: error),
);
''',
};
/// Best practices for provider usage
static const List<String> bestPractices = [
'1. Use @riverpod annotation for new providers (Riverpod 2.x)',
'2. Prefer AsyncNotifierProvider for mutable async state',
'3. Use NotifierProvider for mutable synchronous state',
'4. Use StreamProvider for reactive data streams',
'5. Use Provider for dependency injection and immutable values',
'6. Always handle error states in AsyncValue.when()',
'7. Use ref.invalidate() to refresh provider state',
'8. Implement proper disposal in ref.onDispose()',
'9. Keep providers focused on a single responsibility',
'10. Use meaningful provider names that indicate their purpose',
];
/// Common patterns and solutions
static const Map<String, String> commonPatterns = {
'Combining Providers': '''
@riverpod
String userDisplayName(UserDisplayNameRef ref) {
final user = ref.watch(currentUserProvider);
final settings = ref.watch(userSettingsProvider);
return settings.showFullName
? '\${user.firstName} \${user.lastName}'
: user.username;
}
''',
'Error Handling': '''
@riverpod
class DataNotifier extends _\$DataNotifier {
@override
Future<Data> build() async {
return await _loadData();
}
Future<void> refresh() async {
state = const AsyncValue.loading();
try {
final data = await _loadData();
state = AsyncValue.data(data);
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
// Log error for debugging
ref.read(errorTrackerProvider.notifier).logError(error, stackTrace);
}
}
}
''',
'State Persistence': '''
@riverpod
class PersistentCounter extends _\$PersistentCounter {
@override
int build() {
// Load from storage on build
final storage = ref.watch(storageProvider);
return storage.getInt('counter') ?? 0;
}
void increment() {
state = state + 1;
// Persist state changes
final storage = ref.read(storageProvider);
storage.setInt('counter', state);
}
}
''',
};
/// Performance tips
static const List<String> performanceTips = [
'1. Use select() to watch specific parts of complex state',
'2. Avoid creating providers in build methods',
'3. Use autoDispose for temporary providers',
'4. Keep provider state minimal and focused',
'5. Use family providers for parameterized providers',
'6. Implement proper caching for expensive operations',
'7. Dispose of resources in ref.onDispose()',
'8. Use keepAlive() for providers that should survive rebuilds',
];
}

View File

@@ -0,0 +1,373 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter/foundation.dart';
import '../database/hive_service.dart';
import '../database/models/app_settings.dart';
import '../database/models/cache_item.dart';
import '../database/models/user_preferences.dart';
part 'storage_providers.g.dart';
/// Secure storage configuration
const _secureStorageOptions = FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
iOptions: IOSOptions(
accessibility: KeychainAccessibility.first_unlock_this_device,
),
);
/// Secure storage provider
@riverpod
FlutterSecureStorage secureStorage(SecureStorageRef ref) {
return _secureStorageOptions;
}
/// Secure storage notifier for managing secure data
@riverpod
class SecureStorageNotifier extends _$SecureStorageNotifier {
late FlutterSecureStorage _storage;
@override
Future<Map<String, String>> build() async {
_storage = ref.read(secureStorageProvider);
return await _loadAllSecureData();
}
Future<Map<String, String>> _loadAllSecureData() async {
try {
final allData = await _storage.readAll();
return allData;
} catch (e) {
debugPrint('❌ Error loading secure storage data: $e');
return {};
}
}
/// Store secure value
Future<void> store(String key, String value) async {
try {
await _storage.write(key: key, value: value);
state = AsyncValue.data({...state.value ?? {}, key: value});
debugPrint('🔐 Securely stored: $key');
} catch (error, stackTrace) {
debugPrint('❌ Error storing secure value: $error');
state = AsyncValue.error(error, stackTrace);
}
}
/// Retrieve secure value
Future<String?> retrieve(String key) async {
try {
return await _storage.read(key: key);
} catch (e) {
debugPrint('❌ Error retrieving secure value: $e');
return null;
}
}
/// Delete secure value
Future<void> delete(String key) async {
try {
await _storage.delete(key: key);
final currentData = Map<String, String>.from(state.value ?? {});
currentData.remove(key);
state = AsyncValue.data(currentData);
debugPrint('🗑️ Deleted secure key: $key');
} catch (error, stackTrace) {
debugPrint('❌ Error deleting secure value: $error');
state = AsyncValue.error(error, stackTrace);
}
}
/// Clear all secure storage
Future<void> clearAll() async {
try {
await _storage.deleteAll();
state = const AsyncValue.data({});
debugPrint('🧹 Cleared all secure storage');
} catch (error, stackTrace) {
debugPrint('❌ Error clearing secure storage: $error');
state = AsyncValue.error(error, stackTrace);
}
}
/// Check if key exists
Future<bool> containsKey(String key) async {
try {
return await _storage.containsKey(key: key);
} catch (e) {
debugPrint('❌ Error checking key existence: $e');
return false;
}
}
/// Refresh storage data
Future<void> refresh() async {
state = const AsyncValue.loading();
state = AsyncValue.data(await _loadAllSecureData());
}
}
/// Hive storage providers
@riverpod
Box<AppSettings> appSettingsBox(AppSettingsBoxRef ref) {
return HiveService.appSettingsBox;
}
@riverpod
Box<CacheItem> cacheBox(CacheBoxRef ref) {
return HiveService.cacheBox;
}
@riverpod
Box<UserPreferences> userPreferencesBox(UserPreferencesBoxRef ref) {
return HiveService.userDataBox;
}
/// Hive storage notifier for managing Hive data
@riverpod
class HiveStorageNotifier extends _$HiveStorageNotifier {
@override
Map<String, dynamic> build() {
final appSettingsBox = ref.watch(appSettingsBoxProvider);
final cacheBox = ref.watch(cacheBoxProvider);
final userPreferencesBox = ref.watch(userPreferencesBoxProvider);
return {
'appSettingsCount': appSettingsBox.length,
'cacheItemsCount': cacheBox.length,
'userPreferencesCount': userPreferencesBox.length,
'totalSize': _calculateTotalSize(),
'lastUpdated': DateTime.now().toIso8601String(),
};
}
int _calculateTotalSize() {
try {
final appSettingsBox = ref.read(appSettingsBoxProvider);
final cacheBox = ref.read(cacheBoxProvider);
final userPreferencesBox = ref.read(userPreferencesBoxProvider);
// Rough estimation of storage size
return appSettingsBox.length + cacheBox.length + userPreferencesBox.length;
} catch (e) {
debugPrint('❌ Error calculating storage size: $e');
return 0;
}
}
/// Compact all Hive boxes
Future<void> compactAll() async {
try {
final appSettingsBox = ref.read(appSettingsBoxProvider);
final cacheBox = ref.read(cacheBoxProvider);
final userPreferencesBox = ref.read(userPreferencesBoxProvider);
await Future.wait([
appSettingsBox.compact(),
cacheBox.compact(),
userPreferencesBox.compact(),
]);
_updateStats();
debugPrint('🗜️ Compacted all Hive storage');
} catch (e) {
debugPrint('❌ Error compacting storage: $e');
}
}
/// Clear all cache data
Future<void> clearCache() async {
try {
final cacheBox = ref.read(cacheBoxProvider);
await cacheBox.clear();
_updateStats();
debugPrint('🧹 Cleared all cache data');
} catch (e) {
debugPrint('❌ Error clearing cache: $e');
}
}
/// Get storage statistics
Map<String, dynamic> getStorageStats() {
try {
final appSettingsBox = ref.read(appSettingsBoxProvider);
final cacheBox = ref.read(cacheBoxProvider);
final userPreferencesBox = ref.read(userPreferencesBoxProvider);
return {
'appSettings': {
'count': appSettingsBox.length,
'keys': appSettingsBox.keys.toList(),
'isEmpty': appSettingsBox.isEmpty,
},
'cache': {
'count': cacheBox.length,
'keys': cacheBox.keys.take(10).toList(), // Show only first 10 keys
'isEmpty': cacheBox.isEmpty,
},
'userPreferences': {
'count': userPreferencesBox.length,
'keys': userPreferencesBox.keys.toList(),
'isEmpty': userPreferencesBox.isEmpty,
},
'total': {
'items': appSettingsBox.length + cacheBox.length + userPreferencesBox.length,
'estimatedSize': _calculateTotalSize(),
},
};
} catch (e) {
debugPrint('❌ Error getting storage stats: $e');
return {};
}
}
void _updateStats() {
state = {
...state,
'appSettingsCount': ref.read(appSettingsBoxProvider).length,
'cacheItemsCount': ref.read(cacheBoxProvider).length,
'userPreferencesCount': ref.read(userPreferencesBoxProvider).length,
'totalSize': _calculateTotalSize(),
'lastUpdated': DateTime.now().toIso8601String(),
};
}
}
/// Storage health monitor
@riverpod
class StorageHealthMonitor extends _$StorageHealthMonitor {
@override
Map<String, dynamic> build() {
return {
'isHealthy': true,
'lastCheck': DateTime.now().toIso8601String(),
'errors': <String>[],
'warnings': <String>[],
};
}
/// Perform storage health check
Future<void> performHealthCheck() async {
final errors = <String>[];
final warnings = <String>[];
try {
// Check secure storage
final secureStorage = ref.read(secureStorageProvider);
try {
await secureStorage.containsKey(key: 'health_check');
} catch (e) {
errors.add('Secure storage error: $e');
}
// Check Hive boxes
try {
final appSettingsBox = ref.read(appSettingsBoxProvider);
final cacheBox = ref.read(cacheBoxProvider);
final userPreferencesBox = ref.read(userPreferencesBoxProvider);
if (!appSettingsBox.isOpen) errors.add('App settings box is not open');
if (!cacheBox.isOpen) errors.add('Cache box is not open');
if (!userPreferencesBox.isOpen) errors.add('User preferences box is not open');
// Check for large cache
if (cacheBox.length > 1000) {
warnings.add('Cache has more than 1000 items, consider cleanup');
}
} catch (e) {
errors.add('Hive storage error: $e');
}
state = {
'isHealthy': errors.isEmpty,
'lastCheck': DateTime.now().toIso8601String(),
'errors': errors,
'warnings': warnings,
};
debugPrint('🏥 Storage health check completed: ${errors.isEmpty ? '✅ Healthy' : '❌ Issues found'}');
} catch (e) {
state = {
'isHealthy': false,
'lastCheck': DateTime.now().toIso8601String(),
'errors': ['Health check failed: $e'],
'warnings': warnings,
};
}
}
/// Get health status
bool get isHealthy => state['isHealthy'] ?? false;
/// Get errors
List<String> get errors => List<String>.from(state['errors'] ?? []);
/// Get warnings
List<String> get warnings => List<String>.from(state['warnings'] ?? []);
}
/// Unified storage manager
@riverpod
class StorageManager extends _$StorageManager {
@override
Map<String, dynamic> build() {
final hiveStats = ref.watch(hiveStorageNotifierProvider);
final secureStorageAsync = ref.watch(secureStorageNotifierProvider);
final healthStatus = ref.watch(storageHealthMonitorProvider);
return {
'hive': hiveStats,
'secureStorage': secureStorageAsync.when(
data: (data) => {
'keyCount': data.length,
'isAvailable': true,
},
loading: () => {'isAvailable': false, 'isLoading': true},
error: (_, __) => {'isAvailable': false, 'hasError': true},
),
'health': healthStatus,
'lastUpdated': DateTime.now().toIso8601String(),
};
}
/// Clear all storage
Future<void> clearAllStorage() async {
try {
// Clear secure storage
await ref.read(secureStorageNotifierProvider.notifier).clearAll();
// Clear Hive storage
await ref.read(hiveStorageNotifierProvider.notifier).clearCache();
debugPrint('🧹 Cleared all storage');
} catch (e) {
debugPrint('❌ Error clearing all storage: $e');
}
}
/// Perform maintenance
Future<void> performMaintenance() async {
try {
// Compact storage
await ref.read(hiveStorageNotifierProvider.notifier).compactAll();
// Perform health check
await ref.read(storageHealthMonitorProvider.notifier).performHealthCheck();
debugPrint('🔧 Storage maintenance completed');
} catch (e) {
debugPrint('❌ Error during maintenance: $e');
}
}
/// Get storage overview
Map<String, dynamic> getStorageOverview() {
return state;
}
}

View File

@@ -0,0 +1,151 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'storage_providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$secureStorageHash() => r'9cd02a4033a37568df4c1778f34709abb5853782';
/// Secure storage provider
///
/// Copied from [secureStorage].
@ProviderFor(secureStorage)
final secureStorageProvider =
AutoDisposeProvider<FlutterSecureStorage>.internal(
secureStorage,
name: r'secureStorageProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$secureStorageHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef SecureStorageRef = AutoDisposeProviderRef<FlutterSecureStorage>;
String _$appSettingsBoxHash() => r'9e348c0084f7f23850f09adb2e6496fdbf8f2bdf';
/// Hive storage providers
///
/// Copied from [appSettingsBox].
@ProviderFor(appSettingsBox)
final appSettingsBoxProvider = AutoDisposeProvider<Box<AppSettings>>.internal(
appSettingsBox,
name: r'appSettingsBoxProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$appSettingsBoxHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef AppSettingsBoxRef = AutoDisposeProviderRef<Box<AppSettings>>;
String _$cacheBoxHash() => r'949b55a2b7423b7fa7182b8e45adf02367ab8c7c';
/// See also [cacheBox].
@ProviderFor(cacheBox)
final cacheBoxProvider = AutoDisposeProvider<Box<CacheItem>>.internal(
cacheBox,
name: r'cacheBoxProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$cacheBoxHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef CacheBoxRef = AutoDisposeProviderRef<Box<CacheItem>>;
String _$userPreferencesBoxHash() =>
r'38e2eab12afb00cca5ad2f48bf1f9ec76cc962c8';
/// See also [userPreferencesBox].
@ProviderFor(userPreferencesBox)
final userPreferencesBoxProvider =
AutoDisposeProvider<Box<UserPreferences>>.internal(
userPreferencesBox,
name: r'userPreferencesBoxProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$userPreferencesBoxHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef UserPreferencesBoxRef = AutoDisposeProviderRef<Box<UserPreferences>>;
String _$secureStorageNotifierHash() =>
r'08d6cb392865d7483027fde37192c07cb944c45f';
/// Secure storage notifier for managing secure data
///
/// Copied from [SecureStorageNotifier].
@ProviderFor(SecureStorageNotifier)
final secureStorageNotifierProvider = AutoDisposeAsyncNotifierProvider<
SecureStorageNotifier, Map<String, String>>.internal(
SecureStorageNotifier.new,
name: r'secureStorageNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$secureStorageNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SecureStorageNotifier = AutoDisposeAsyncNotifier<Map<String, String>>;
String _$hiveStorageNotifierHash() =>
r'5d91bf162282fcfbef13aa7296255bb87640af51';
/// Hive storage notifier for managing Hive data
///
/// Copied from [HiveStorageNotifier].
@ProviderFor(HiveStorageNotifier)
final hiveStorageNotifierProvider = AutoDisposeNotifierProvider<
HiveStorageNotifier, Map<String, dynamic>>.internal(
HiveStorageNotifier.new,
name: r'hiveStorageNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$hiveStorageNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$HiveStorageNotifier = AutoDisposeNotifier<Map<String, dynamic>>;
String _$storageHealthMonitorHash() =>
r'1d52e331a84bd59a36055f5e8963eaa996f9c235';
/// Storage health monitor
///
/// Copied from [StorageHealthMonitor].
@ProviderFor(StorageHealthMonitor)
final storageHealthMonitorProvider = AutoDisposeNotifierProvider<
StorageHealthMonitor, Map<String, dynamic>>.internal(
StorageHealthMonitor.new,
name: r'storageHealthMonitorProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$storageHealthMonitorHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$StorageHealthMonitor = AutoDisposeNotifier<Map<String, dynamic>>;
String _$storageManagerHash() => r'8e017d34c8c574dd2777d6478af3cd921448b080';
/// Unified storage manager
///
/// Copied from [StorageManager].
@ProviderFor(StorageManager)
final storageManagerProvider =
AutoDisposeNotifierProvider<StorageManager, Map<String, dynamic>>.internal(
StorageManager.new,
name: r'storageManagerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$storageManagerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$StorageManager = AutoDisposeNotifier<Map<String, dynamic>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@@ -0,0 +1,231 @@
import 'package:flutter/material.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../database/models/app_settings.dart';
import '../database/repositories/settings_repository.dart';
part 'theme_providers.g.dart';
/// Theme mode enumeration
enum AppThemeMode {
light('light'),
dark('dark'),
system('system');
const AppThemeMode(this.value);
final String value;
static AppThemeMode fromString(String value) {
return AppThemeMode.values.firstWhere(
(mode) => mode.value == value,
orElse: () => AppThemeMode.system,
);
}
}
/// Settings repository provider
@riverpod
SettingsRepository settingsRepository(SettingsRepositoryRef ref) {
return SettingsRepository();
}
/// Current app settings provider
@riverpod
class AppSettingsNotifier extends _$AppSettingsNotifier {
late SettingsRepository _repository;
@override
Future<AppSettings> build() async {
_repository = ref.read(settingsRepositoryProvider);
return _repository.getSettings();
}
/// Update theme mode
Future<void> updateThemeMode(AppThemeMode mode) async {
state = const AsyncValue.loading();
try {
await _repository.updateThemeMode(mode.value);
final updatedSettings = _repository.getSettings();
state = AsyncValue.data(updatedSettings);
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
}
}
/// Update locale
Future<void> updateLocale(String locale) async {
state = const AsyncValue.loading();
try {
await _repository.updateLocale(locale);
final updatedSettings = _repository.getSettings();
state = AsyncValue.data(updatedSettings);
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
}
}
/// Update notifications enabled
Future<void> updateNotificationsEnabled(bool enabled) async {
state = const AsyncValue.loading();
try {
await _repository.updateNotificationsEnabled(enabled);
final updatedSettings = _repository.getSettings();
state = AsyncValue.data(updatedSettings);
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
}
}
/// Update analytics enabled
Future<void> updateAnalyticsEnabled(bool enabled) async {
state = const AsyncValue.loading();
try {
await _repository.updateAnalyticsEnabled(enabled);
final updatedSettings = _repository.getSettings();
state = AsyncValue.data(updatedSettings);
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
}
}
/// Set custom setting
Future<void> setCustomSetting(String key, dynamic value) async {
state = const AsyncValue.loading();
try {
await _repository.setCustomSetting(key, value);
final updatedSettings = _repository.getSettings();
state = AsyncValue.data(updatedSettings);
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
}
}
/// Reset to default settings
Future<void> resetToDefault() async {
state = const AsyncValue.loading();
try {
await _repository.resetToDefault();
final updatedSettings = _repository.getSettings();
state = AsyncValue.data(updatedSettings);
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
}
}
/// Refresh settings from storage
Future<void> refresh() async {
state = const AsyncValue.loading();
try {
final settings = _repository.getSettings();
state = AsyncValue.data(settings);
} catch (error, stackTrace) {
state = AsyncValue.error(error, stackTrace);
}
}
}
/// Current theme mode provider
@riverpod
AppThemeMode currentThemeMode(CurrentThemeModeRef ref) {
final settingsAsync = ref.watch(appSettingsNotifierProvider);
return settingsAsync.when(
data: (settings) => AppThemeMode.fromString(settings.themeMode),
loading: () => AppThemeMode.system,
error: (_, __) => AppThemeMode.system,
);
}
/// Effective theme mode provider (resolves system theme)
@riverpod
ThemeMode effectiveThemeMode(EffectiveThemeModeRef ref) {
final themeMode = ref.watch(currentThemeModeProvider);
switch (themeMode) {
case AppThemeMode.light:
return ThemeMode.light;
case AppThemeMode.dark:
return ThemeMode.dark;
case AppThemeMode.system:
return ThemeMode.system;
}
}
/// Is dark mode active provider
@riverpod
bool isDarkMode(IsDarkModeRef ref) {
final themeMode = ref.watch(currentThemeModeProvider);
switch (themeMode) {
case AppThemeMode.light:
return false;
case AppThemeMode.dark:
return true;
case AppThemeMode.system:
// Get platform brightness from MediaQuery
// This will be handled at the widget level
return false; // Default fallback
}
}
/// Current locale provider
@riverpod
Locale currentLocale(CurrentLocaleRef ref) {
final settingsAsync = ref.watch(appSettingsNotifierProvider);
return settingsAsync.when(
data: (settings) => Locale(settings.locale),
loading: () => const Locale('en'),
error: (_, __) => const Locale('en'),
);
}
/// Settings stream provider for reactive updates
@riverpod
class SettingsStreamNotifier extends _$SettingsStreamNotifier {
@override
Stream<AppSettings> build() {
final repository = ref.read(settingsRepositoryProvider);
return repository.watchSettings();
}
}
/// Theme preferences provider for quick access
@riverpod
class ThemePreferences extends _$ThemePreferences {
@override
Map<String, dynamic> build() {
final settingsAsync = ref.watch(appSettingsNotifierProvider);
return settingsAsync.when(
data: (settings) => {
'themeMode': settings.themeMode,
'isDarkMode': AppThemeMode.fromString(settings.themeMode) == AppThemeMode.dark,
'locale': settings.locale,
'analyticsEnabled': settings.analyticsEnabled,
'notificationsEnabled': settings.notificationsEnabled,
},
loading: () => {
'themeMode': 'system',
'isDarkMode': false,
'locale': 'en',
'analyticsEnabled': false,
'notificationsEnabled': true,
},
error: (_, __) => {
'themeMode': 'system',
'isDarkMode': false,
'locale': 'en',
'analyticsEnabled': false,
'notificationsEnabled': true,
},
);
}
}

View File

@@ -0,0 +1,153 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'theme_providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$settingsRepositoryHash() =>
r'0203e31bb994214ce864bf95a7afa14a8a14b812';
/// Settings repository provider
///
/// Copied from [settingsRepository].
@ProviderFor(settingsRepository)
final settingsRepositoryProvider =
AutoDisposeProvider<SettingsRepository>.internal(
settingsRepository,
name: r'settingsRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$settingsRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef SettingsRepositoryRef = AutoDisposeProviderRef<SettingsRepository>;
String _$currentThemeModeHash() => r'6cd4101e1d0f6cbd7851f117872cd49253fe0564';
/// Current theme mode provider
///
/// Copied from [currentThemeMode].
@ProviderFor(currentThemeMode)
final currentThemeModeProvider = AutoDisposeProvider<AppThemeMode>.internal(
currentThemeMode,
name: r'currentThemeModeProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$currentThemeModeHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef CurrentThemeModeRef = AutoDisposeProviderRef<AppThemeMode>;
String _$effectiveThemeModeHash() =>
r'd747fdd8489857c595ae766ee6a9497c4ad360c0';
/// Effective theme mode provider (resolves system theme)
///
/// Copied from [effectiveThemeMode].
@ProviderFor(effectiveThemeMode)
final effectiveThemeModeProvider = AutoDisposeProvider<ThemeMode>.internal(
effectiveThemeMode,
name: r'effectiveThemeModeProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$effectiveThemeModeHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef EffectiveThemeModeRef = AutoDisposeProviderRef<ThemeMode>;
String _$isDarkModeHash() => r'e76c5818694a33e63bd0a8ba0b7494d7ee12cff5';
/// Is dark mode active provider
///
/// Copied from [isDarkMode].
@ProviderFor(isDarkMode)
final isDarkModeProvider = AutoDisposeProvider<bool>.internal(
isDarkMode,
name: r'isDarkModeProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$isDarkModeHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef IsDarkModeRef = AutoDisposeProviderRef<bool>;
String _$currentLocaleHash() => r'c3cb4000a5eefa748ca41e50818b27323e61605a';
/// Current locale provider
///
/// Copied from [currentLocale].
@ProviderFor(currentLocale)
final currentLocaleProvider = AutoDisposeProvider<Locale>.internal(
currentLocale,
name: r'currentLocaleProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$currentLocaleHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef CurrentLocaleRef = AutoDisposeProviderRef<Locale>;
String _$appSettingsNotifierHash() =>
r'3a66de82c9b8f75bf34ffc7755b145a6d1e9c21e';
/// Current app settings provider
///
/// Copied from [AppSettingsNotifier].
@ProviderFor(AppSettingsNotifier)
final appSettingsNotifierProvider =
AutoDisposeAsyncNotifierProvider<AppSettingsNotifier, AppSettings>.internal(
AppSettingsNotifier.new,
name: r'appSettingsNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$appSettingsNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AppSettingsNotifier = AutoDisposeAsyncNotifier<AppSettings>;
String _$settingsStreamNotifierHash() =>
r'1c1e31439ee63edc3217a20c0198bbb2aff6e033';
/// Settings stream provider for reactive updates
///
/// Copied from [SettingsStreamNotifier].
@ProviderFor(SettingsStreamNotifier)
final settingsStreamNotifierProvider = AutoDisposeStreamNotifierProvider<
SettingsStreamNotifier, AppSettings>.internal(
SettingsStreamNotifier.new,
name: r'settingsStreamNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$settingsStreamNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$SettingsStreamNotifier = AutoDisposeStreamNotifier<AppSettings>;
String _$themePreferencesHash() => r'71778e4afc614e1566d4a15131e2ab5d2302e57b';
/// Theme preferences provider for quick access
///
/// Copied from [ThemePreferences].
@ProviderFor(ThemePreferences)
final themePreferencesProvider = AutoDisposeNotifierProvider<ThemePreferences,
Map<String, dynamic>>.internal(
ThemePreferences.new,
name: r'themePreferencesProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$themePreferencesHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ThemePreferences = AutoDisposeNotifier<Map<String, dynamic>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member