Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

13 — State Management (Search Module)

Client state architecture for global search. Conventions from 00-shared/06_State_Management.md (flutter_bloc, get_it DI, repository layer with dio, no server-state libs). Search is a pure server-state surface — no local persistence (forward-looking) history aside.


1. Cubits

1.1 SearchCubit (results screen + in-shell bar share one instance)

StateFields
SearchInitial
SearchLoadingprevious SearchResults? (kept for dimmed view)
SearchSuccessSearchResults (query, entityType, list, PaginationMeta)
SearchEmptyquery (raw, for copy)
SearchErrorApiException(code, status, message), query

SearchResults model mirrors SearchResult[] + PaginationMeta (search.service.ts:10-16, pagination-query.dto.ts:32-39).

1.2 SearchHistoryCubit (forward-looking)

Device-storage recent queries (ledger B5 — server has no history endpoint).

2. Repository

SearchRepository (single method):

Future<SearchPage> search({
  required String q, String? entityType, int page = 1, int limit = 20,
}) async => /* GET /search, maps envelope → SearchPage */;
  • Only layer touching HTTP (00-shared/06 §2); throws typed ApiException (401/403/429/5xx — 12 §5).
  • No caching in this layer today — matches backend (Redis unwired, redis-cache.service.ts:16-19); add (proposed) TTL cache keyed search:{q}:{type}:{page} if latency budgets demand (00-shared/10 §1).

3. Debounce + stale-guard flow (exact)

sequenceDiagram
    participant U as User
    participant B as SearchBarField
    participant C as SearchCubit
    participant R as SearchRepository
    participant S as API (GET /search)

    U->>B: type "rah"
    B->>B: restart 300ms Timer
    U->>B: type "rahul sharma"
    B->>B: restart 300ms Timer (cancel prev)
    B->>C: onQueryChanged("rahul sharma")
    C->>C: guardToken++; emit Loading(prevResults)
    C->>R: search(q, page=1)
    R->>S: GET /search?q=rahul+sharma
    S-->>R: 200 {data, meta}
    R-->>C: SearchPage
    C->>C: token fresh? yes → emit Success(results)
    Note over C: stale responses (token mismatch) discarded
    U->>B: type "rahul sharm"
    B->>C: onQueryChanged (timer)
    Note over C,R,S: same flow; old in-flight response now stale → dropped

Rules (06 §1.3, 10 §2):

  • Timer restarts on every keystroke; fires only after 300 ms of silence.
  • Submit (textInputAction.search) skips the timer.
  • Guard token: each new query invalidates earlier in-flight responses — prevents out-of-order rendering.
  • Pagination: loadMore() guarded by meta.hasNext (pagination-query.dto.ts:52) and !isLoadingPage; appends, never replaces.

4. Entity-type filter state

  • entityType lives in the cubit, not in route params (keeps back-navigation cheap, 10 §3); route params optional (forward-looking) for deep links.
  • See-all sets entityType + page 1; clearing restores the aggregate.
  • Back from detail: cubit survives (app-shell scope), so results are intact.

5. Failure & retry

  • SearchErrorAppErrorState retry re-emits the same query (token++, page 1).
  • 401 → global session-expiry flow (00-shared/10 §3), query preserved.
  • Offline → banner + last SearchSuccess retained (10 §7).

6. What is NOT in state

  • No per-type totals (server cannot provide them beyond the fetched page — search.service.ts:44-49).
  • No debounce state (owned by the widget's Timer).
  • No history/suggestions ((forward-looking)).
  • No Redis/persistent cache ((proposed)).