Current User Notifier Deep Dive (current_user_provider.dart)
🎯 Senior Engineering Objective
Deconstruct production profile caching, optimistic in-memory state mutations (updateLocationLocal), AsyncValue.guard() error handling, and ref.mounted unmount guards inside current_user_provider.dart.
1. Why Does This File Exist?
In Transwave, Firebase Auth only manages identity verification (email/password, Google sign-in). However, the application requires rich backend domain user profiles containing rating scores, carrier KYC status, verified documents, and GPS coordinates.
current_user_provider.dart acts as the single source of truth for the logged-in user's backend profile. It fetches data via GET /users/me, caches it globally across screens, and provides optimistic in-memory update methods for GPS location changes without spamming the backend API.
2. Who Uses This File? (Codebase Search)
A full workspace audit reveals 14 consumer sites depending on currentUserProvider:
| Consumer File | Consumption Pattern | Purpose & Action |
|---|---|---|
lib/features/auth/providers/login_controller.dart |
ref.read(currentUserProvider.notifier).refresh() |
Triggers backend user profile fetch immediately after Firebase login sync. |
lib/features/home/screens/map_screen.dart |
ref.watch(currentUserProvider) |
Determines whether user operates as Sender or Carrier to adjust map markers. |
lib/features/profile/screens/public_user_profile_screen.dart |
ref.watch(currentUserProvider) |
Displays avatar, full name, phone number, and verification badges. |
lib/shared/widgets/app_drawer.dart |
ref.watch(currentUserProvider.select((u) => u.value?.fullName)) |
Renders top drawer header user greeting. |
3. Dependency Graph
4. Execution Timeline
MapScreen calls ref.watch(currentUserProvider).AsyncLoading() while authRepository.fetchCurrentUser() calls GET /users/me.AsyncData(User). All watching UI widgets rebuild simultaneously with clean profile data.Section A: Visual AsyncValue State Machine
The state machine powering CurrentUserNotifier undergoes the following deterministic state transitions:
Section B: AsyncValue API Deep Dive
| AsyncValue Method | Behavior & Semantics | When to Use in Production |
|---|---|---|
AsyncValue.guard(fn) |
Executes async closure; returns AsyncData on success or AsyncError on exception. |
Inside Notifier mutations (e.g. refresh()). |
state.requireValue |
Returns data synchronously; throws StateError if state is loading or error. |
Only inside if (state.hasValue) optimistic update checks. |
state.hasValue |
Boolean returning true if state holds valid data (even while refreshing). | Checking data availability before executing optimistic copyWith. |
state.when(...) |
Exhaustive pattern match requiring data, loading, and error callbacks. |
Inside Flutter widget build() methods for complete UI safety. |
Section C: State Evolution & Optimistic Location Updates
updateLocationLocal() mutates state in-memory instantly using currentUser.copyWith(lastKnownLat, lastKnownLng).Section D: Detailed Mutation Analysis
@riverpod
class CurrentUserNotifier extends _$CurrentUserNotifier {
@override
Future<User> build() async {
final repo = ref.watch(authRepositoryProvider);
return repo.fetchCurrentUser();
}
/// Re-fetches user profile from backend.
Future<void> refresh() async {
if (!ref.mounted) return;
state = const AsyncValue.loading();
final result = await AsyncValue.guard(() async {
final repo = ref.read(authRepositoryProvider);
return repo.fetchCurrentUser();
});
if (!ref.mounted) return;
state = result;
}
/// Updates user location in local state without refetching.
void updateLocationLocal({required double lat, required double lng}) {
if (state.hasValue) {
final currentUser = state.requireValue;
state = AsyncValue.data(currentUser.copyWith(
lastKnownLat: lat,
lastKnownLng: lng,
));
}
}
}
Section E: Rebuild & Performance Analysis
When updateLocationLocal() updates user coordinates, widgets watching currentUserProvider directly will rebuild.
Widgets that only require the user's name (e.g. AppDrawer) should use ref.watch(currentUserProvider.select((u) => u.value?.fullName)). This prevents the drawer from re-rendering when GPS coordinates update!
Section F: Architectural Comparison Matrix
| Architecture Pattern | In-Memory Mutation | Async Safety | Rationale & Fit |
|---|---|---|---|
| AsyncNotifier (Current) | 🟢 Excellent (copyWith) |
🟢 Full AsyncValue |
Best Fit: Profile state requiring network fetch + optimistic local edits. |
| FutureProvider | 🔴 Impossible | 🟢 Good | Unsuited: Read-only; cannot support updateLocationLocal. |
| BLoC / Cubit | 🟢 Good | 🟡 Verbose | Heavy event boilerplate for simple profile updates. |
Section G: 10 Senior Flutter Interview Questions & Answers
Answer: FutureProvider is strictly read-only. AsyncNotifier allows exposing custom operational methods (like refresh() or updateLocationLocal()) to mutate cached state in-memory.
Answer: It executes an asynchronous closure inside a try/catch block. If successful, it returns AsyncData(value); if an exception is caught, it returns AsyncError(error, stackTrace) safely without crashing the app.
Answer: If a provider is auto-disposed while an async network request is pending, setting state = result on an unmounted notifier throws a fatal state modification exception.
Answer: state.value returns nullable data (`T?`). state.requireValue returns non-nullable `T` directly, but throws a StateError if state does not contain value data.
Answer: It reads the existing user value, creates an updated copy via copyWith, and reassigns state = AsyncValue.data(updatedUser) instantly without waiting for HTTP response round-trips.
📊 Senior Engineering Scorecard & Review
authRepositoryProvider via ref.watch().ref.mounted guards across all async boundaries.