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 (Notifications Module)

Flutter client. App-wide conventions in 00-shared/06 (State Management) and 00-shared/11 (Flutter App Architecture); this file is the module slice.


1. Cubits

1.1 NotificationsCubit — inbox list

StateFields
NotificationsInitial
NotificationsLoading— (first page)
NotificationsLoadeditems, page, hasNext, totalItems, loadingMore, unreadOnly
NotificationsEmptyunreadOnly (drives copy)
NotificationsErrormessage, retryable

Events

  • LoadFirstPage({ bool unreadOnly }) — reset + fetch ?page=1&limit=20[&unreadOnly].
  • LoadMore() — guarded by hasNext && !loadingMore; appends, dedupes by _id (offset pagination can duplicate on insert-on-top, 10 §5).
  • MarkOneRead(id) — optimistic flip, then PATCH /:id/read; revert on error.
  • MarkAllRead() — optimistic clear, then PATCH /read-all; revert on error.
  • Refresh() — reload page 1, keep scroll position.

Dependencies: NotificationsApi (see 15 §4), UnreadCountCubit (count reconciliation after mark mutations).

1.2 UnreadCountCubit — shell badge

StateFields
UnreadCountStatecount (int ≥ 0), loaded (bool — false → hide badge silently, 06 §3)

Events: Refresh() (GET /unread-count), Decrement() (optimistic, from NotificationsCubit), Reset() (after mark-all), Increment() (forward-looking: realtime via WebSocket /wsEND_TO_END_USER_FLOWS.md:773-775).

Refresh triggers: app foreground, inbox open, after any mark mutation (server value wins over optimistic).

2. State Flow

flowchart TD
    A[AppShell] -->|badge| UC[UnreadCountCubit]
    A -->|"GET /unread-count"| UC

    B[InboxScreen] --> NC[NotificationsCubit]
    NC -->|"GET /notifications?page&limit&unreadOnly"| API

    subgraph Mutations
      NC -->|"PATCH /:id/read"| API
      NC -->|"PATCH /read-all"| API
    end

    API -->|"envelope {data, meta}"| NC
    NC -->|"onMutation success → Refresh"| UC
    UC -->|count| B[Badge Widget]
    NC -->|items| T[NotificationListTile]

3. Consistency Rules

  1. Server is truth after every mutation: optimistic UI → request → reconcile with response (doc readAt, or fresh unread-count).
  2. Badge = server count; optimistic decrements are cosmetic and re-synced on foreground/inbox-open.
  3. unreadOnly filter change resets the list (new LoadFirstPage).
  4. Pagination state lives in the Cubit, never in widget locals (00-shared/06 — single source per feature).
  5. Offline hydration: cache last Loaded via app-wide cache layer (00-shared/06 §hydration); refresh on reconnect.

4. What NOT to store in state

  • Relative-time strings (recomputed on rebuild).
  • Icon mappings (pure functions in one file — 11 §3).
  • Auth token / tenant (shell concerns, 00-shared/06).