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

15 — Flutter Implementation Guide (Notifications Module)

Client implementation guide. App architecture baseline in 00-shared/11, state conventions in 00-shared/06. All endpoints from 12_API_Mapping.


1. Data Model

class Notification {
  final String id;                 // _id
  final String recipientId;
  final String type;               // server enum string; render unknown types generically
  final String title;
  final String body;
  final Map<String, dynamic>? data;
  final DateTime? readAt;          // null = unread
  final DateTime createdAt;
  bool get unread => readAt == null;
}

fromJson must tolerate data = null/absent (notification.schema.ts:28-29).

2. Pagination Envelope

class PageMeta {
  final int page, limit, totalItems, totalPages;
  final bool hasNext, hasPrevious;
}

Parsed from meta (pagination-query.dto.ts:32-39); deserialize data + meta inside the shared envelope (00-shared/07).

3. API Client

MethodCall
fetchInbox({page, limit, unreadOnly})GET /api/v1/notifications
fetchUnreadCount()GET /api/v1/notifications/unread-count{count}
markRead(id)PATCH /api/v1/notifications/:id/read — treat 200 null as success no-op
markAllRead()PATCH /api/v1/notifications/read-all

Base path /api/v1 from app config; auth header via shared client (00-shared/11 §auth).

4. Repository

  • NotificationsRepository (domain layer): thin wrapper over the API client; holds the Cubits' data sources; no widget imports.
  • Client-side dedupe by id on append (offset pagination drift, 10 §5).

5. Cubits

NotificationsCubit + UnreadCountCubit per 13_State_Management — states, events, and the consistency rules (optimistic → reconcile with server truth) are specified there verbatim.

6. Widgets

WidgetFile suggestionBuilds
NotificationInboxScreenfeatures/notifications/inbox_screen.dartAppBar (title + MarkAllReadAction), list, PaginationFooter, EmptyState, ErrorPanel
NotificationListTilefeatures/notifications/widgets/notification_list_tile.dartC1 (07)
NotificationBadgefeatures/notifications/widgets/notification_badge.dartC3 — used by AppShell
notification_icons.dartfeatures/notifications/widgets/notification_icons.darttype→icon map (11 §3)
  • List: ListView.builder + ScrollController near-end detection (0.7 * maxScrollExtent) → LoadMore.
  • Pull-to-refresh: RefreshIndicatorRefresh() event.
  • Tap: optimistic MarkOneRead then optional nav via data.targetUrl (proposed) — v1 no navigation.

7. Type→Icon Map

IconData iconFor(String type) => switch (type) {
  'welcome' => Icons.celebration_outlined,
  'email_verified' => Icons.verified_outlined,
  'verification_resent' => Icons.mail_outline,
  'password_reset' => Icons.lock_reset,
  _ => Icons.notifications_outlined,
};

8. Badge Integration

  • AppShell owns UnreadCountCubit; refreshes on foreground via WidgetsBindingObserver.
  • After inbox mutations, NotificationsCubit emits → UnreadCountCubit.Refresh().
  • Hide badge when loaded == false.
  • Route /notifications registered in the shell router.
  • data.targetUrl convention (v2): tap → mark read → go_router.push(targetUrl). Validate targetUrl against an allowlist before navigation.

10. Push / FCM (forward-looking — do not implement in v1)

Blocked on: device-token endpoint (END_TO_END_USER_FLOWS.md:788), mobile-app decision (IMPLEMENTATION_PLAN.md:859), channel prefs (Notifications.md:27-28). When unblocked: register token after login, map data payload → notification tap → same deep-link path as §9.