Provider final
This document outlines the consistent pattern used across all service providers in the services package. All providers now follow the modern @riverpod annotation pattern for better consistency, testing support, and code generation.
All services now follow the same pattern:
@riverpod
class ServiceName extends _$ServiceName {
@override
ServiceState build() {
return const ServiceState();
}
// Service methods...
}
@riverpod
bool serviceIsLoading(Ref ref) {
return ref.watch(serviceNameProvider).isLoading;
}
@riverpod
String? serviceError(Ref ref) {
return ref.watch(serviceNameProvider).error;
}
All services require:
import 'package:riverpod_annotation/riverpod_annotation.dart';part 'service_name.g.dart';- Running
dart run build_runner buildto generate providers
@riverpod
class AuthService extends _$AuthService {
@override
AuthState build() {
return const AuthState();
}
// Methods: trySignIn, signIn, signUp, signOut, etc.
}
// Convenience providers
@riverpod bool authIsAuthenticated(Ref ref) => ...
@riverpod bool authIsLoading(Ref ref) => ...
@riverpod String? authError(Ref ref) => ...
@riverpod UserModel? currentUser(Ref ref) => ...
Usage:
// Access service
final authState = ref.watch(authServiceProvider);
final authService = ref.read(authServiceProvider.notifier);
// Use convenience providers
final isAuth = ref.watch(authIsAuthenticatedProvider);
final user = ref.watch(currentUserProvider);
// Call methods
await authService.signIn(attempt, rememberMe);
@riverpod
class NetworkService extends _$NetworkService {
@override
NetworkServiceState build() {
_startMonitoring();
return NetworkServiceState();
}
// Methods: forceCheck, updateStatus, etc.
}
// Convenience providers
@riverpod bool isOnline(Ref ref) => ...
@riverpod bool isOffline(Ref ref) => ...
@riverpod NetworkStatus networkStatus(Ref ref) => ...
Usage:
// Access service
final networkState = ref.watch(networkServiceProvider);
final networkService = ref.read(networkServiceProvider.notifier);
// Use convenience providers
final online = ref.watch(isOnlineProvider);
final status = ref.watch(networkStatusProvider);
// Call methods
await networkService.forceCheck();
@riverpod
class PledgeService extends _$PledgeService {
@override
PledgeServiceState build() {
return const PledgeServiceState();
}
// Methods: fetchMyPledges, createPledge, updatePledge, etc.
}
// Convenience providers
@riverpod bool pledgeIsLoading(Ref ref) => ...
@riverpod List<PledgeModel> pledgesList(Ref ref) => ...
@riverpod int pledgeCount(Ref ref) => ...
Usage:
// Access service
final pledgeState = ref.watch(pledgeServiceProvider);
final pledgeService = ref.read(pledgeServiceProvider.notifier);
// Use convenience providers
final pledges = ref.watch(pledgesListProvider);
final count = ref.watch(pledgeCountProvider);
// Call methods
await pledgeService.fetchMyPledges();
await pledgeService.createPledge(pledge);
@riverpod
class UserService extends _$UserService {
@override
UserServiceState build() {
return const UserServiceState();
}
// Methods: fetchUser, searchUsers, getPublicProfile, etc.
}
// Convenience providers
@riverpod bool userIsLoading(Ref ref) => ...
@riverpod UserModel? fetchedUser(Ref ref) => ...
Usage:
// Access service
final userState = ref.watch(userServiceProvider);
final userService = ref.read(userServiceProvider.notifier);
// Use convenience providers
final loading = ref.watch(userIsLoadingProvider);
final user = ref.watch(fetchedUserProvider);
// Call methods
await userService.fetchUser(userId);
final users = await userService.searchUsers(query);
All services now have consistent testing support through the _testing_utilities.dart file:
import 'package:services/provider/_testing_utilities.dart';
// Create test container
final container = createTestContainer();
// Test with overrides
final container = createTestContainer(
overrides: [
authServiceProvider.overrideWith(() => MockAuthService()),
],
);
// Use convenience functions
expect(isAuthenticated(container), true);
expect(isOnline(container), true);
expect(getPledgeCount(container), 5);
AuthService: UsedStateNotifierwithRefparameterNetworkService: UsedStateNotifierProvidermanuallyPledgeService: Used@riverpod(correct)UserService: Used@riverpod(correct)
- All services: Use
@riverpodannotation - All services: Generate providers automatically
- All services: Have consistent convenience providers
- All services: Support testing with same pattern
When adding new services, follow this template:
import 'package:riverpod_annotation/riverpod_annotation.dart';
// ... other imports
part 'your_service.g.dart';
@riverpod
class YourService extends _$YourService {
@override
YourServiceState build() {
return const YourServiceState();
}
// Your service methods here
Future<void> someMethod() async {
state = state.copyWith(isLoading: true);
try {
// Implementation
state = state.copyWith(isLoading: false);
} catch (e) {
state = state.copyWith(error: e.toString(), isLoading: false);
}
}
}
// Convenience providers
@riverpod
bool yourServiceIsLoading(Ref ref) {
return ref.watch(yourServiceProvider).isLoading;
}
@riverpod
String? yourServiceError(Ref ref) {
return ref.watch(yourServiceProvider).error;
}
- Consistency: All services follow the same pattern
- Testing: Easy to mock and test with overrides
- Code Generation: Automatic provider generation
- Type Safety: Better type inference and compile-time checks
- Performance: Optimal re-rendering with fine-grained providers
- Maintainability: Single pattern to learn and maintain
class LoginWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isLoading = ref.watch(authIsLoadingProvider);
final error = ref.watch(authErrorProvider);
final authService = ref.read(authServiceProvider.notifier);
return Column(
children: [
if (isLoading) CircularProgressIndicator(),
if (error != null) Text('Error: $error'),
ElevatedButton(
onPressed: () => authService.signIn(attempt, true),
child: Text('Sign In'),
),
],
);
}
}
class NetworkIndicator extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isOnline = ref.watch(isOnlineProvider);
return Container(
color: isOnline ? Colors.green : Colors.red,
child: Text(isOnline ? 'Online' : 'Offline'),
);
}
}
This consistent pattern makes the codebase more maintainable, testable, and easier to understand for all developers.