// coverage:ignore-file
// ๐ PAGE STATE TEMPLATE: Standardized Riverpod page state management pattern
//
// This template provides a consistent approach for managing page-specific state
// with automatic persistence using SharedPreferences. Unlike services that manage
// business logic, page state providers focus on UI state preservation.
//
// Key Features:
// โ
Automatic state persistence and restoration
// โ
Standardized naming conventions
// โ
Convenience providers for easy access
// โ
Utility methods for common operations
// โ
Error handling with graceful fallbacks
// โ
Testing support with provider overrides
import 'package:presentation/_prelude.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'dart:convert';
part '_page_state_template.g.dart';
// =============================================================================
// ๐ฏ TEMPLATE: Replace 'PageName' with your actual page name (e.g., 'Home', 'Stripe')
// ๐ฏ TEMPLATE: Replace 'PageState' with your actual state class (e.g., 'FilterState', 'StripeOnboardStartState')
// =============================================================================
/// ๐ SHARED PREFERENCES PROVIDER
/// Provides access to SharedPreferences for persistence
@riverpod
SharedPreferences sharedPreferences(Ref ref) {
final prefs = ScreenTemplateState.sharedPreferences;
if (prefs == null) {
throw StateError(
'SharedPreferences not yet initialized. Make sure you navigate to a screen that extends ScreenTemplate first.',
);
}
return prefs;
}
/// ๐ PERSISTENCE SERVICE: Handles saving/loading page state
@riverpod
PageNamePersistenceService pageNamePersistenceService(Ref ref) {
final prefs = ref.watch(sharedPreferencesProvider);
return PageNamePersistenceService(prefs);
}
/// ๐ฆ PERSISTENCE SERVICE CLASS
class PageNamePersistenceService {
final SharedPreferences _prefs;
static const String _pageStateKey = 'page_name_state';
static const String _allStatesKey = 'all_page_states';
PageNamePersistenceService(this._prefs);
/// Save page state to persistent storage
Future<void> savePageState(PageState pageState) async {
try {
final json = pageState.toJson(); // Assumes your state has toJson()
await _prefs.setString(_pageStateKey, jsonEncode(json));
// Track this key for bulk clearing
final allStates = _prefs.getStringList(_allStatesKey) ?? [];
if (!allStates.contains(_pageStateKey)) {
allStates.add(_pageStateKey);
await _prefs.setStringList(_allStatesKey, allStates);
}
} catch (e) {
LOG.e('Failed to save page state: $e');
// Don't rethrow - persistence failure shouldn't break the app
}
}
/// Load page state from persistent storage
PageState? loadPageState() {
try {
final jsonString = _prefs.getString(_pageStateKey);
if (jsonString != null) {
final json = jsonDecode(jsonString) as Map<String, dynamic>;
return PageState.fromJson(json); // Assumes your state has fromJson()
}
} catch (e) {
LOG.e('Failed to load page state: $e');
// Return null to use default state
}
return null;
}
/// Clear persisted page state
Future<void> clearPageState() async {
try {
await _prefs.remove(_pageStateKey);
} catch (e) {
LOG.e('Failed to clear page state: $e');
}
}
/// Clear all persisted page states
Future<void> clearAllPersistedStates() async {
try {
final allStates = _prefs.getStringList(_allStatesKey) ?? [];
for (final key in allStates) {
await _prefs.remove(key);
}
await _prefs.remove(_allStatesKey);
} catch (e) {
LOG.e('Failed to clear all page states: $e');
}
}
}
/// ๐ PAGE STATE SERVICE: Main state management with persistence
@riverpod
class PageNameStateService extends _$PageNameStateService {
PageNamePersistenceService? _persistenceService;
@override
PageState build() {
_persistenceService ??= ref.read(pageNamePersistenceServiceProvider);
// Try to load persisted state
try {
final persistedState = _persistenceService!.loadPageState();
if (persistedState != null) {
LOG.i('Loaded persisted page state');
return persistedState;
}
} catch (e) {
LOG.e('Failed to load persisted page state: $e');
}
// Return default state
LOG.i('Using default page state');
return PageState.defaultState(); // Define this in your state class
}
// =============================================================================
// ๐ฏ STANDARDIZED UTILITY METHODS
// =============================================================================
/// Update the entire page state and persist it
Future<void> updateState(PageState newState) async {
state = newState;
await _persistState();
}
/// Reset to default state and persist
Future<void> resetToDefault() async {
final defaultState = PageState.defaultState();
await updateState(defaultState);
}
/// Clear error state (if your state has error handling)
void clearError() {
// Implement based on your state structure
// state = state.copyWith(error: null);
}
/// Refresh/reload state data
Future<void> refresh() async {
// Implement refresh logic based on your needs
// This might reload from persistence or reset to default
await resetToDefault();
}
/// Reset to initial state without persistence
void reset() {
state = PageState.defaultState();
}
// =============================================================================
// ๐ง PRIVATE HELPER METHODS
// =============================================================================
/// Persist current state
Future<void> _persistState() async {
try {
await _persistenceService?.savePageState(state);
} catch (e) {
LOG.e('Failed to persist page state: $e');
// Don't rethrow - persistence failure shouldn't break the app
}
}
/// Check if state has been modified from default
bool get isModified {
final defaultState = PageState.defaultState();
return !_areStatesEqual(state, defaultState);
}
/// Compare two states for equality
bool _areStatesEqual(PageState state1, PageState state2) {
// Implement based on your state structure
// This might use == operator or custom comparison logic
return state1 == state2;
}
}
// =============================================================================
// ๐ฏ CONVENIENCE PROVIDERS: Easy access to specific state aspects
// =============================================================================
/// Check if page state is loading (if applicable)
@riverpod
bool pageNameIsLoading(Ref ref) {
final state = ref.watch(pageNameStateServiceProvider);
// Implement based on your state structure
// return state.isLoading ?? false;
return false; // Default implementation
}
/// Get current error state (if applicable)
@riverpod
String? pageNameError(Ref ref) {
final state = ref.watch(pageNameStateServiceProvider);
// Implement based on your state structure
// return state.error;
return null; // Default implementation
}
/// Check if state has been modified from default
@riverpod
bool pageNameIsModified(Ref ref) {
final notifier = ref.watch(pageNameStateServiceProvider.notifier);
return notifier.isModified;
}
/// Get specific data from state (customize based on your needs)
@riverpod
DataType? pageNameSpecificData(Ref ref) {
final state = ref.watch(pageNameStateServiceProvider);
// return state.specificField;
return null; // Implement based on your state structure
}
// =============================================================================
// ๐งน CACHE MANAGEMENT: For clearing persisted page states
// =============================================================================
/// Page cache manager for clearing persisted data
@riverpod
class PageNameCacheManager extends _$PageNameCacheManager {
@override
void build() {
// Service provider - no state needed
}
/// Clear all page state cache
Future<void> clearAllPageCache() async {
try {
final persistenceService = ref.read(pageNamePersistenceServiceProvider);
await persistenceService.clearAllPersistedStates();
// Reset the page state provider
ref.invalidate(pageNameStateServiceProvider);
LOG.i('Cleared all page cache');
} catch (e) {
LOG.e('Error clearing page cache: $e');
rethrow;
}
}
/// Clear only this page's cache
Future<void> clearPageCache() async {
try {
final persistenceService = ref.read(pageNamePersistenceServiceProvider);
await persistenceService.clearPageState();
// Reset the page state provider
ref.invalidate(pageNameStateServiceProvider);
LOG.i('Cleared page cache');
} catch (e) {
LOG.e('Error clearing page cache: $e');
rethrow;
}
}
}
// =============================================================================
// ๐ฏ USAGE EXAMPLES AND DOCUMENTATION
// =============================================================================
// USAGE IN WIDGETS:
class MyPageWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch the entire state
final pageState = ref.watch(pageNameStateServiceProvider);
// Watch specific aspects
final isLoading = ref.watch(pageNameIsLoadingProvider);
final error = ref.watch(pageNameErrorProvider);
final isModified = ref.watch(pageNameIsModifiedProvider);
// Update state
if (someCondition) {
ref.read(pageNameStateServiceProvider.notifier).updateState(newState);
}
return YourWidgetTree();
}
}
// TESTING:
testWidgets('page state test', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
pageNameStateServiceProvider.overrideWith(() => MockPageStateService()),
],
child: MyApp(),
),
);
expect(container.read(pageNameIsModifiedProvider), false);
// ... rest of test
});
// MIGRATION FROM HYDRATED_BLOC:
//
// Before:
// - class MyPageCubit extends HydratedCubit<MyPageState>
// - context.read<MyPageCubit>().updateState(newState)
// - BlocBuilder<MyPageCubit, MyPageState>
//
// After:
// - @riverpod class MyPageStateService extends _$MyPageStateService
// - ref.read(myPageStateServiceProvider.notifier).updateState(newState)
// - Consumer(builder: (context, ref, child) { final state = ref.watch(...) })