Implement SearchDebounceUtility core class
epic-contact-search-service-infrastructure-task-001 — Create the SearchDebounceUtility class that wraps a RxDart or Timer-based debounce mechanism with a configurable delay (default 300ms). Expose a clean debounced stream from a raw input stream, enforce minimum query length (e.g., 2 characters), and cancel in-flight operations on new input arrival.
Acceptance Criteria
Technical Requirements
Implementation Notes
Prefer rxdart's debounceTime operator over a raw Timer — it handles cancellation, stream lifecycle, and backpressure correctly out of the box. The class should use a StreamController internally and pipe the filtered+debounced values to a public stream. If rxdart is already in pubspec.yaml use it; if not, evaluate adding it as this utility will be reused across multiple search features. Minimum query length filtering should happen BEFORE debouncing (filter then debounce) to avoid firing a debounce timer for a keystroke that would be filtered anyway.
Expose the class as final — it should not be extended. The dispose() method should call close() on internal StreamControllers and cancel subscriptions; document that callers are responsible for calling dispose() when the utility is no longer needed (typically in a BLoC or Riverpod notifier's dispose/close).
Testing Requirements
Unit tests using flutter_test with fake_async to control timer advancement without real wall-clock delays. Test cases: (1) single value emitted after delay, (2) rapid successive values — only last emitted, (3) value below min length filtered, (4) value at exact min length passes through, (5) dispose prevents further emissions. Use fake_async.elapse() to advance time precisely. No real async timers — all time-dependent behaviour tested with fake_async.
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.