Implement online/offline routing in ContactSearchService
epic-contact-search-service-infrastructure-task-004 — Build the connectivity-aware routing layer inside ContactSearchService. When the device is online, route queries to the SupabaseSearchRepository; when offline, route to the OfflineSearchRepository (Drift). Use a connectivity stream or provider to detect state changes and switch sources transparently without UI-layer involvement.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Use constructor injection for both repositories and the connectivity stream: ContactSearchService(this._supabaseRepo, this._offlineRepo, this._connectivityStream). In the class body, subscribe to _connectivityStream in the constructor and cache the latest ConnectivityResult in a private field _currentConnectivity. Each search() call reads _currentConnectivity synchronously β no awaiting the stream on every call. The fallback logic (Supabase throws β use Drift) should be a try/catch around the Supabase call; catch SocketException and AuthException specifically, not all exceptions (a Supabase 400 Bad Request should bubble up as an error, not silently fallback to cache).
The isFromCache flag on SearchResult is important for the UI to optionally show a 'showing cached results' banner β include it in the model even if the UI does not use it initially. Register ContactSearchService as a singleton in the Riverpod provider tree so its connectivity subscription persists for the app lifetime.
Testing Requirements
Unit tests in test/features/contact_search/contact_search_service_test.dart. Mock both repositories with mockito/mocktail. Provide a StreamController
All tests must be synchronous or use fake_async β no real timers.
Cancelling in-flight Supabase HTTP requests via RxDart switchMap may not actually abort the server-side query if the Supabase Dart client does not support request cancellation tokens, leading to wasted API calls and potential race conditions where a slow earlier response arrives after a faster later one.
Mitigation & Contingency
Mitigation: Audit the supabase-flutter client's cancellation support before implementation. Use RxDart switchMap to discard stale emissions even if HTTP cancellation is unavailable, ensuring only the latest result reaches the UI.
Contingency: If race conditions surface in testing, add a query sequence counter to tag each emission and discard any response whose sequence number is lower than the most recently emitted one.
Connectivity detection used to route between online and offline repositories may have latency or give false positives on flaky connections, causing the service to attempt Supabase queries that time out instead of falling back to the cache promptly.
Mitigation & Contingency
Mitigation: Use a try/catch with a short timeout on Supabase calls. On network error, immediately fall back to the offline repository and emit a cached result with an offline indicator rather than surfacing an error state.
Contingency: If the timeout-based fallback proves insufficient, implement a connection health check stream that pre-validates connectivity before each query batch.