Auth service
// New - ONE state class with everything needed
class AuthState {
final UserModel? user; // If user exists, authenticated
final String? error; // If error exists, has error
final bool isLoading; // Simple loading flag
// All derived state comes from these 3 fields:
bool get isAuthenticated => user != null;
bool get isAnonymous => user == null;
bool get hasError => error != null;
bool get requiresVerification => user?.verified == false;
}
// New - simple, direct method calls
final authService = ref.read(authServiceSimpleProvider.notifier);
await authService.signIn(emailAttempt);
await authService.signUp(googleAttempt);
await authService.signOut();
await authService.trySignIn(); // Uses stored credentials
authService.setAnonymous(); // Immediate
authService.clearError(); // Immediate
// New - state always makes sense
if (state.isAuthenticated) {
// state.user is guaranteed to be non-null
print('Welcome ${state.user!.email}');
}
if (state.hasError) {
// state.error is guaranteed to be non-null
showError(state.error!);
}
// New - all user-related providers delegate to AuthService
@riverpod
UserModel? currentUser(Ref ref) {
return ref.watch(authServiceSimpleProvider).user; // Single source
}
@riverpod
bool currentUserIsAuthenticated(Ref ref) {
return ref.watch(authServiceSimpleProvider).isAuthenticated; // Single source
}
@riverpod
bool currentUserIsLoading(Ref ref) {
final authLoading = ref.watch(authServiceSimpleProvider).isLoading;
final userLoading = ref.watch(currentUserServiceProvider).isLoading;
return authLoading || userLoading; // Combines both loading states clearly
}
// AuthService: Manages authentication and user state
class AuthService {
Future<void> signIn(AuthAttempt attempt) { /* handles auth */ }
Future<void> signOut() { /* handles auth */ }
void updateUser(UserModel user) { /* updates user state */ }
}
// CurrentUserService: Manages user operations only
class CurrentUserService {
Future<void> updateUser(UserModel user) { /* API calls, then delegates to AuthService */ }
Future<void> updateUserField(String field, dynamic value) { /* user operations */ }
Future<void> refreshUser() { /* refresh from server */ }
}
The test suite provides excellent examples of how to use the new unified service:
Old Pattern:
// Don't use these anymore
ref.watch(currentUserServiceProvider)
ref.read(currentUserServiceProvider.notifier).updateUser(user)
New Pattern:
// Use these instead
ref.watch(authServiceProvider) // or specific convenience providers
ref.read(authServiceProvider.notifier).updateUser(user)
// Or use convenience providers for cleaner code
ref.watch(currentUserProvider) // Just the user
ref.watch(currentUserIsLoadingProvider) // Just loading state
ref.watch(currentUserDisplayNameProvider) // Smart display name
// Get the service
final service = container.read(authServiceProvider.notifier);
// Set authenticated user
service.state = AuthState(user: testUser);
// Update user
await service.updateUser(updatedUser);
// Update specific field
await service.updateUserField('displayName', 'New Name');
// Access state via providers
final user = container.read(currentUserProvider);
final isLoading = container.read(currentUserIsLoadingProvider);
final displayName = container.read(currentUserDisplayNameProvider);
// Clear errors
service.clearError();
// Check error state
final hasError = container.read(authServiceProvider).hasError;
final errorMessage = container.read(currentUserErrorProvider);
// Check auth state
final authState = ref.watch(authServiceSimpleProvider);
if (authState.isAuthenticated) {
// User is signed in - state.user is guaranteed non-null
print('Welcome ${authState.user!.email}');
}
// Sign in
final authService = ref.read(authServiceSimpleProvider.notifier);
await authService.signIn(EmailAttempt.signInWithCredentials(
email: 'user@example.com',
password: 'password',
));
// Handle result immediately - no events needed
if (authState.hasError) {
showError(authState.error!);
} else if (authState.isAuthenticated) {
navigateToHome();
}
Replace event calls with direct methods:
// Before await triggerEvent(AuthEventType.signIn, AuthEventData(...)); // After await authService.signIn(attempt);Update state checks:
// Before if (authState.isAuthenticated && authState.currentUser != null) // After if (authState.isAuthenticated) // user is guaranteed non-nullReplace provider references:
// Before ref.watch(authServiceProvider) // After ref.watch(authServiceSimpleProvider)
The simplified version maintains all the functionality while being much easier to understand, maintain, and extend.