15 — Flutter Implementation Guide (Notifications Module)
- 1. Data Model
- 2. Pagination Envelope
- 3. API Client
- 4. Repository
- 5. Cubits
- 6. Widgets
- 7. Type→Icon Map
- 8. Badge Integration
- 9. Deep Links (proposed)
- 10. Push / FCM (forward-looking — do not implement in v1)
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
| Method | Call |
|---|---|
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
idon 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
| Widget | File suggestion | Builds |
|---|---|---|
NotificationInboxScreen | features/notifications/inbox_screen.dart | AppBar (title + MarkAllReadAction), list, PaginationFooter, EmptyState, ErrorPanel |
NotificationListTile | features/notifications/widgets/notification_list_tile.dart | C1 (07) |
NotificationBadge | features/notifications/widgets/notification_badge.dart | C3 — used by AppShell |
notification_icons.dart | features/notifications/widgets/notification_icons.dart | type→icon map (11 §3) |
- List:
ListView.builder+ScrollControllernear-end detection (0.7 * maxScrollExtent) →LoadMore. - Pull-to-refresh:
RefreshIndicator→Refresh()event. - Tap: optimistic
MarkOneReadthen optional nav viadata.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 viaWidgetsBindingObserver. - After inbox mutations,
NotificationsCubitemits →UnreadCountCubit.Refresh(). - Hide badge when
loaded == false.
9. Deep Links (proposed)
- Route
/notificationsregistered in the shell router. data.targetUrlconvention (v2): tap → mark read →go_router.push(targetUrl). ValidatetargetUrlagainst 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.