Implement parallel fetch logic in ContactDetailService
epic-contact-detail-and-edit-service-layer-task-002 — Implement the core data-fetching method that fires contact profile, activity history, and assignment status requests in parallel using Future.wait or equivalent Dart async primitives. Map repository responses into the ContactDetailLoaded state and emit appropriate loading and error states with typed error messages.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Use Future.wait([profileFuture, historyFuture, assignmentFuture]) for true parallelism. Destructure the result list positionally. Wrap the entire Future.wait in a try/catch and inspect the exception type to determine which ContactDetailErrorType to emit. For idempotency in a BLoC, check if the current state is already ContactDetailLoading before emitting it again; in Riverpod, use a CancelToken or store the in-flight Future ref.
Do NOT use separate sequential awaits — this doubles latency unnecessarily. Map Supabase PostgrestException codes to ContactDetailErrorType: code '42501' or HTTP 403 → permissionDenied, empty result set → notFound, network/timeout → networkFailure. Sort activityHistory by timestamp descending before constructing the loaded state.
Testing Requirements
Unit tests using flutter_test with mocked repositories: (1) Verify loading state is emitted before any response arrives. (2) Verify ContactDetailLoaded is emitted when all three mocks return successfully. (3) Verify ContactDetailError with networkFailure is emitted when any repository throws a network exception. (4) Verify ContactDetailError with notFound is emitted when the contact repository returns null.
(5) Verify ContactDetailError with permissionDenied is emitted for 403 responses. (6) Verify that when loadContact is called twice rapidly, only one set of states is emitted for the second call (idempotency/debounce). Use fake_async or similar to control async timing in tests.
Parallel fetching of profile, activity history, and assignment status from contact-detail-service may produce race conditions where partial state is emitted to the UI before all fetches complete, resulting in flickering or incorrect loading indicators.
Mitigation & Contingency
Mitigation: Use Future.wait or a single composed BLoC event that only emits a loaded state once all three futures resolve. Define a strict state machine: initial → loading → loaded/error with no intermediate partial-loaded states emitted to the UI.
Contingency: If parallelism proves unreliable in testing, fall back to sequential fetching with a combined loading indicator. The 500ms target may need to be renegotiated with stakeholders if sequential fetching exceeds it on slow connections.
The partial-field update pattern in contact-edit-service assumes the contact record has not changed between when the edit screen was loaded and when the save is submitted. Concurrent edits by another coordinator could cause the earlier editor's save to silently overwrite the later one.
Mitigation & Contingency
Mitigation: Include an updated_at timestamp in the PATCH request and configure Supabase to reject updates where the server-side timestamp differs from the client's version. Return a 409-equivalent error that the service maps to a user-readable conflict message.
Contingency: If optimistic locking is too complex for initial delivery, implement a simple 'reload and retry' flow: on save error, reload the contact detail and prompt the coordinator to re-apply their changes manually.