Riverpod vs bloc
This guide shows how to replace HydratedBloc with clean, testable, and maintainable Riverpod code for persisting page states like FilterState.
class HomeScreenCubit extends HydratedCubit<HomeScreenCubitState> {
HomeScreenCubit() : super(HomeScreenCubitState(filterState: HomeFilterState()));
@override
HomeScreenCubitState? fromJson(Map<String, dynamic> json) {
return HomeScreenCubitState(
filterState: FilterState.fromJson(json[FilterState.jsonKey]),
);
}
@override
Map<String, dynamic>? toJson(HomeScreenCubitState state) {
return {
FilterState.jsonKey: FilterState.toJson(state.filterState),
};
}
}
final homeFilterStateProvider = StateNotifierProvider<HomeFilterStateNotifier, FilterState>((ref) {
return HomeFilterStateNotifier(ref);
});
class HomeFilterStateNotifier extends StateNotifier<FilterState> {
// Clean, testable, maintainable implementation
// Automatic persistence with SharedPreferences
// Clear separation of concerns
}
- π§ͺ More Testable: Easy to mock providers and test state changes
- π§ Better Maintainability: Clear separation between business logic and persistence
- π¦ Smaller Bundle Size: No need for HydratedBloc dependency
- π Better Performance: Granular reactivity with Riverpod
- π§Ή Easy Cache Management: Built-in cache clearing functionality
- π± Better State Management: Automatic state restoration when returning to pages
The provider handles:
- β Automatic state persistence with SharedPreferences
- β State restoration when returning to the page
- β Easy cache clearing for user preferences
- β Granular access to different filter aspects
- β Error handling for persistence failures
Key changes:
// Before: StatefulWidget with BlocProvider
class HomeScreen extends StatefulWidget
// After: ConsumerStatefulWidget with Riverpod
class HomeScreen extends ConsumerStatefulWidget
// Before: BlocBuilder and context.watch<HomeScreenCubit>()
final filterState = context.watch<HomeScreenCubit>().state.filterState;
// After: Direct provider watching
final filterState = ref.watch(homeFilterStateProvider);
// Before: context.read<HomeScreenCubit>().setFilterState()
context.read<HomeScreenCubit>().setFilterState(newState);
// After: Provider notifier calls
ref.read(homeFilterStateProvider.notifier).updateFilterState(newState);
Shows how to properly initialize SharedPreferences with Riverpod:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final container = await AppInitialization.initializeApp();
runApp(
UncontrolledProviderScope(
container: container,
child: MyApp(),
),
);
}
class MyWidget extends ConsumerWidget {
@override
Widget (BuildContext context, WidgetRef ref) {
// Watch the entire filter state
final filterState = ref.watch(homeFilterStateProvider);
// Or watch specific aspects
final showFilters = ref.watch(activeShowFiltersProvider);
final contentFilters = ref.watch(activeContentFiltersProvider);
return YourWidgetTree();
}
}
// Reset to default
ref.read(homeFilterStateProvider.notifier).resetToDefault();
// Apply specific changes
ref.read(homeFilterStateProvider.notifier).applyFilterChange(change);
// Update entire state
ref.read(homeFilterStateProvider.notifier).updateFilterState(newState);
final cacheManager = ref.read(appCacheManagerProvider);
// Clear only filter cache
await cacheManager.clearFilterCache();
// Clear all app cache
await cacheManager.clearAllApplicationCache();
// Complex setup with mock storage
testWidgets('filter state test', (tester) async {
final storage = MockStorage();
HydratedBloc.storage = storage;
// Complex setup...
});
// Clean, simple mocking
testWidgets('filter state test', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
homeFilterStateProvider.overrideWith((ref) => MockFilterNotifier()),
],
child: MyApp(),
),
);
});
Install Dependencies:
dependencies: hooks_riverpod: ^2.4.9 shared_preferences: ^2.2.2Remove HydratedBloc Dependencies:
# Remove these # hydrated_bloc: ^9.1.3 # path_provider: ^2.1.1Replace BlocProvider with ProviderScope in your app root
Convert StatefulWidgets to ConsumerStatefulWidgets
Replace context.watch/read with ref.watch/read
Set up SharedPreferences initialization
- β Persistent State: FilterState automatically persists and restores
- β Easy Cache Clearing: Built-in functionality for clearing all preserved states
- β Better Performance: Granular reactivity with Riverpod
- β Cleaner Code: Separation of business logic and persistence
- β Better Testing: Easy mocking and testing
- β Future-Proof: Extensible for additional page states
The user returns to HomeScreen and their previous FilterState is automatically restored, and they can easily clear all preserved states when needed!