Bootstrap Notification BLoC and Realtime subscription
epic-in-app-notification-centre-services-task-006 — Create the NotificationBloc class extending Bloc. On bloc initialisation (NotificationInitialised event), inject and subscribe to the Supabase Realtime Subscription Service to receive INSERT events on the notifications table scoped to the current user's role. Load the initial notification page from the repository and emit NotificationLoaded state. Register the subscription stream as a BLoC event source using emit.forEach so stream lifecycle is tied to bloc lifecycle.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Use `emit.forEach(stream, onData: (event) => add(NotificationRealtimeInsert(event)))` inside the `NotificationInitialised` handler to bind stream lifetime to bloc lifetime — this is the idiomatic BLoC pattern and automatically cancels on bloc close. Separate the initial load and subscription setup: `await Future.wait([_loadInitialPage(emit), _subscribeRealtime(emit)])` so neither blocks the other. Define abstract interfaces `INotificationRepository`, `IRealtimeSubscriptionService`, `INotificationReadStateService`, and `IRoleAwareNotificationFilterService` — BLoC depends only on these. Use `on
Emit unread count by filtering `notifications.where((n) => !n.isRead).length` on the initial load result.
Testing Requirements
Unit tests (flutter_test + bloc_test) covering: (1) NotificationInitialised triggers repository fetch and emits Loading then Loaded, (2) repository failure emits NotificationError, (3) Realtime subscription is set up after successful init (verify mock subscription service called), (4) bloc.close() triggers subscription cancellation (verify mock dispose called), (5) NotificationLoaded state contains correct unread count from initial fetch. Use bloc_test's `expect` DSL for state sequence assertions. Mock all dependencies with mockito or manual test doubles. Avoid using real Supabase clients in unit tests.
Add an integration smoke test verifying the bloc can be instantiated with real Supabase test credentials and emits at least one NotificationLoaded state.
A Realtime INSERT event arriving during an in-flight mark-all-read operation can cause the new notification to be incorrectly marked read in the optimistic state update, silently hiding it from the user.
Mitigation & Contingency
Mitigation: Process Realtime events sequentially in the BLoC event queue using bloc_concurrency's sequential transformer. The mark-all-read event should only affect notifications whose IDs were fetched before the operation started.
Contingency: Add a reconciliation step after mark-all-read that re-fetches the unread count from the repository and corrects the BLoC state if it diverges from the server value.
A coordinator assigned to many peer mentors may trigger a query returning hundreds or thousands of notifications. Without pagination and query optimisation, the initial load will be slow and memory-heavy.
Mitigation & Contingency
Mitigation: Enforce server-side pagination (50 items per page) in the Role-Aware Filter's query predicates. Add a composite index on (org_id, user_id, created_at DESC) and profile query plans before shipping.
Contingency: If query performance is insufficient for large coordinator scopes, introduce a server-side RPC function that pre-aggregates visible notification IDs and returns only the first page, deferring full scope resolution to lazy-load.
If the read-state optimistic update rolls back frequently due to intermittent connectivity, users will observe notifications toggling between read and unread, creating confusion and distrust of the feature.
Mitigation & Contingency
Mitigation: Queue failed mutations in a local retry store and re-attempt on next connectivity event using a connectivity-aware retry service. Show a non-intrusive banner if offline rather than applying optimistic updates.
Contingency: Disable optimistic updates for mark-as-read in low-connectivity scenarios detected by the connectivity provider, instead showing a loading indicator until server confirmation.