Manual vs generated
Answer: YES, you can absolutely replace manual provider code with generated code to reduce duplication!
Here’s a comprehensive comparison showing both approaches:
import 'package:presentation/_prelude.dart';
import '../barrel.dart';
/// Manual providers - lots of boilerplate
final sharedPreferencesProvider = Provider<SharedPreferences>((ref) {
throw UnimplementedError('SharedPreferences must be overridden in main()');
});
final homeFilterStateProvider = StateNotifierProvider<HomeFilterStateNotifier, FilterState>((ref) {
return HomeFilterStateNotifier(ref);
});
class HomeFilterStateNotifier extends StateNotifier<FilterState> {
final Ref _ref;
late final FilterPersistenceService _persistenceService;
HomeFilterStateNotifier(this._ref) : super(HomeFilterState()) {
_persistenceService = _ref.read(filterPersistenceServiceProvider);
_loadPersistedState();
}
// ... rest of implementation
}
/// Lots of manual convenience providers
final activeShowFiltersProvider = Provider<List<FilterField>?>((ref) {
final filterState = ref.watch(homeFilterStateProvider);
return filterState.show?.active;
});
// ... more manual providers
import 'package:presentation/_prelude.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../barrel.dart';
part 'home_filter_provider.g.dart';
/// π GENERATED: SharedPreferences provider
@riverpod
SharedPreferences sharedPreferences(Ref ref) {
throw UnimplementedError('SharedPreferences must be overridden in main()');
}
/// π GENERATED: Persistence service provider
@riverpod
FilterPersistenceService filterPersistenceService(Ref ref) {
final prefs = ref.watch(sharedPreferencesProvider);
return FilterPersistenceService(prefs);
}
/// π GENERATED: Main filter service (replaces StateNotifier boilerplate)
@riverpod
class HomeFilterService extends _$HomeFilterService {
late final FilterPersistenceService _persistenceService;
@override
FilterState build() {
_persistenceService = ref.read(filterPersistenceServiceProvider);
// Try to load persisted state
try {
final persistedState = _persistenceService.loadHomeFilterState();
if (persistedState != null) return persistedState;
} catch (e) {
LOG.e('Failed to load persisted filter state: $e');
}
return HomeFilterState();
}
/// All your business logic methods stay the same
Future<void> updateFilterState(FilterState newState) async {
state = newState;
try {
await _persistenceService.saveHomeFilterState(newState);
} catch (e) {
LOG.e('Failed to persist filter state: $e');
}
}
Future<void> resetToDefault() async {
await updateFilterState(HomeFilterState());
}
Future<void> applyFilterChange(FilterChange change) async {
final newState = FilterState(
show: state.show,
filter: state.filter,
sort: state.sort,
);
newState.remove(change);
await updateFilterState(newState);
}
// Convenience getters (no separate providers needed!)
List<FilterField>? get activeShowFilters => state.show?.active;
List<FilterField>? get activeContentFilters => state.filter?.active;
List<FilterField>? get activeSortFilters => state.sort?.active;
}
/// π GENERATED: Convenience providers (much cleaner!)
@riverpod
List<FilterField>? activeShowFilters(Ref ref) {
return ref.watch(homeFilterServiceProvider).show?.active;
}
@riverpod
List<FilterField>? activeContentFilters(Ref ref) {
return ref.watch(homeFilterServiceProvider).filter?.active;
}
@riverpod
class AppCacheManager extends _$AppCacheManager {
@override
void build() {} // Service provider
Future<void> clearAllApplicationCache() async {
final persistenceService = ref.read(filterPersistenceServiceProvider);
await persistenceService.clearAllPersistedStates();
ref.invalidate(homeFilterServiceProvider);
}
}
/// Backward compatibility alias
final homeFilterStateProvider = homeFilterServiceProvider;
| Aspect | Manual Providers | Generated Code (@riverpod) |
|---|---|---|
| Boilerplate | β Lots of repetitive code | β Minimal boilerplate |
| Type Safety | β Good | β Excellent (generated types) |
| Code Size | β ~150+ lines | β ~80 lines |
| Maintenance | β More error-prone | β Less error-prone |
| Performance | β Good | β Same (no overhead) |
| IDE Support | β Good | β Excellent (generated code) |
| Testing | β Good | β Easier to mock |
| Hot Reload | β Works | β Works |
- Before: 150+ lines of boilerplate
- After: 80 lines of business logic
Generated providers have perfect type inference:
// Generated: Perfect types automatically
final filterState = ref.watch(homeFilterServiceProvider); // FilterState
final showFilters = ref.watch(activeShowFiltersProvider); // List<FilterField>?
// Manual: Sometimes needs explicit typing
final filterState = ref.watch(homeFilterStateProvider); // Could be dynamic
Generated code updates automatically when you change method signatures.
All your services follow the same generated pattern, like your PledgeService.
dependencies:
riverpod_annotation: ^2.3.3
dev_dependencies:
build_runner: ^2.4.7
riverpod_generator: ^2.3.9
Replace your manual StateNotifierProvider with @riverpod class.
dart run build_runner build
No changes needed! The generated provider names remain the same.
// Your HomeScreen usage stays exactly the same!
class HomeScreen extends ConsumerStatefulWidget {
@override
Widget build(BuildContext context) {
final filterState = ref.watch(homeFilterStateProvider);
// ... rest of your code unchanged
}
}
// Identical usage - no changes needed!
class HomeScreen extends ConsumerStatefulWidget {
@override
Widget build(BuildContext context) {
final filterState = ref.watch(homeFilterStateProvider); // Same!
// ... rest of your code unchanged
}
}