Framework
Successfully standardized the presentation package provider framework for page state management using Riverpod. This framework is specifically designed for managing page-specific UI state with automatic persistence, complementing but distinct from the business logic service framework.
- ✅ Main Service:
HomeFilterService- Manages FilterState with persistence - ✅ Convenience Providers:
homeFilterIsLoading(consistency with service pattern)homeFilterError(consistency with service pattern)homeFilterIsModified(tracks changes from default)homeFilterActiveShowFilters(quick access to show filters)homeFilterActiveContentFilters(quick access to content filters)homeFilterActiveSortFilters(quick access to sort filters)homeFilterShowCount,homeFilterContentCount,homeFilterSortCounthomeFilterTotalCount(total active filter count)
- ✅ Persistence Service:
FilterPersistenceService- SharedPreferences integration - ✅ Standardized Error Handling: Graceful failure for persistence issues
- ✅ Bulk Clearing: Track all filter states for mass cleanup
- ✅ State Class:
StripeOnboardingState- Replaces HydratedCubit state - ✅ Main Service:
StripeOnboardingService- Full Riverpod state management - ✅ Persistence Service:
StripeOnboardingPersistenceService- SharedPreferences - ✅ Convenience Providers:
stripeOnboardingCurrency(current currency selection)stripeOnboardingAccount(current account selection)stripeOnboardingCurrencySelected(validation helper)stripeOnboardingAccountSelected(validation helper)stripeOnboardingReadyToProceed(form completion validation)
- ✅ Universal Template: Complete template for any page state service
- ✅ Comprehensive Documentation: Usage examples, migration guide
- ✅ Testing Support: Provider override patterns included
// Main service pattern
@riverpod
class PageNameStateService extends _$PageNameStateService {
// Main state management logic
}
// Convenience providers pattern
@riverpod
bool pageNameIsLoading(Ref ref) => // loading state
String? pageNameError(Ref ref) => // error state
bool pageNameIsModified(Ref ref) => // modification tracking
DataType pageNameSpecificData(Ref ref) => // specific data access
// Persistence service pattern
class PageNamePersistenceService {
Future<void> savePageState(PageState state) async {
// Save to SharedPreferences with error handling
}
PageState? loadPageState() {
// Load from SharedPreferences with fallback to default
}
}
Every page state service includes:
Future<void> updateState(PageState newState) // Update and persist
Future<void> resetToDefault() // Reset to default state
Future<void> clearPersistedState() // Clear storage
void clearError() // Clear errors (consistency)
Future<void> refresh() // Refresh/reload state
void reset() // Reset without persistence
bool get isModified // Check if modified from default
- ✅ Graceful Degradation: Persistence failures don’t break the app
- ✅ Fallback to Default: Always provide working default state
- ✅ Comprehensive Logging: All errors logged for debugging
// Easy provider overrides for testing
testWidgets('page state test', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
pageNameStateServiceProvider.overrideWith(() => MockPageStateService()),
],
child: MyApp(),
),
);
});
class StripeOnboardStartCubit extends HydratedCubit<StripeOnboardStartState> {
void setCurrency(StoredCurrencyModel newCurrency) {
emit(state.copyWith(currency: newCurrency));
}
@override
Map<String, dynamic>? toJson(StripeOnboardStartState state) {
return {
'currency': state.currency.toJson,
'account': state.account.toJson,
};
}
}
@riverpod
class StripeOnboardingService extends _$StripeOnboardingService {
Future<void> setCurrency(StoredCurrencyModel newCurrency) async {
final newState = state.copyWith(currency: newCurrency);
await updateState(newState); // Automatic persistence
}
}
// Usage in widgets
final currency = ref.watch(stripeOnboardingCurrencyProvider);
final isReady = ref.watch(stripeOnboardingReadyToProceedProvider);
class FilterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch specific aspects
final showFilters = ref.watch(activeShowFiltersProvider);
final totalCount = ref.watch(homeFilterTotalCountProvider);
final isModified = ref.watch(homeFilterIsModifiedProvider);
// Update state
if (resetPressed) {
ref.read(homeFilterServiceProvider.notifier).resetToDefault();
}
return YourFilterUI();
}
}
class StripeOnboardingWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final currency = ref.watch(stripeOnboardingCurrencyProvider);
final account = ref.watch(stripeOnboardingAccountProvider);
final canProceed = ref.watch(stripeOnboardingReadyToProceedProvider);
return Column(
children: [
CurrencySelector(
selected: currency,
onChanged: (newCurrency) {
ref.read(stripeOnboardingServiceProvider.notifier)
.setCurrency(newCurrency);
},
),
AccountSelector(
selected: account,
onChanged: (newAccount) {
ref.read(stripeOnboardingServiceProvider.notifier)
.setAccount(newAccount);
},
),
ProceedButton(enabled: canProceed),
],
);
}
}
- ✅ Same naming patterns (
xxxIsLoading,xxxError,xxxService) - ✅ Same utility methods (
clearError,refresh,reset) - ✅ Same testing approaches (provider overrides)
- ✅ Automatic Persistence: State survives app restarts
- ✅ Modification Tracking: Know when state changed from default
- ✅ Validation Helpers: Ready-to-use form validation providers
- ✅ Bulk State Management: Clear all page states at once
- ✅ Template-Driven: Copy template, replace placeholders, done
- ✅ IntelliSense Friendly: Full type safety with generated providers
- ✅ Error Resilient: Persistence failures don’t crash the app
- ✅ Testing Ready: Easy mocking with provider overrides
- ✅ Granular Reactivity: Widgets only rebuild when specific data changes
- ✅ Efficient Persistence: Only save when state actually changes
- ✅ Lazy Loading: State only loads when actually accessed
The presentation package now has two complementary frameworks:
- Purpose: Business logic, API calls, data management
- Examples:
AuthService,PledgeService,UserService - Features: Loading states, error handling, data caching
- Purpose: UI state, form data, user preferences
- Examples:
HomeFilterService,StripeOnboardingService - Features: Automatic persistence, modification tracking, validation
The standardization is complete! 🚀
Both frameworks follow identical patterns for consistency while serving their distinct purposes. The presentation package now provides a robust, testable, and maintainable foundation for all page state management needs.