Problem
NotificationService.initialize() can be called from multiple user flows, but only local notification plugin setup is guarded. Repeated initialization can register additional FCM foreground/opened-app listeners and token-refresh listeners, causing duplicate notification handling, duplicate navigation, and repeated FCM token registration.
Evidence
lib/core/services/notification_service.dart always runs background handler registration, permission request, _setupMessageHandlers(), and requestNotificationToken() from initialize().
setupFlutterNotifications() is guarded, but there is no equivalent guard for whole-service initialization, FCM message handlers, initial-message handling, or token-refresh subscription.
requestNotificationToken() calls _messaging.onTokenRefresh.listen(...) every time it runs, without storing/canceling/reusing the subscription.
_setupMessageHandlers() calls FirebaseMessaging.onMessage.listen(...) and FirebaseMessaging.onMessageOpenedApp.listen(...) every time it runs, without storing/canceling/reusing subscriptions.
- Call sites can re-enter initialization from startup permission flow, notification-allow screen, and My Page permission changes.
- Existing tests cover single initialization counts, but no test asserts that repeated
initialize() calls do not create duplicate listeners or duplicate token registration attempts.
Proposed direction
Make NotificationService.initialize() idempotent at the service seam. Track initialization state and in-flight initialization, store StreamSubscriptions for foreground, opened-app, and token refresh listeners, and ensure setup/listener registration runs once per service lifecycle.
Keep setupFlutterNotifications() idempotent, but do not rely on it as the only guard.
Acceptance criteria
- Calling
NotificationService.initialize() twice concurrently or sequentially registers FCM foreground, opened-app, and token-refresh listeners only once.
- Repeated initialization does not call
getInitialMessage() more than intended or navigate twice for the same initial/opened message.
- Repeated initialization does not register the same FCM token refresh callback multiple times or duplicate token registration requests for one refresh event.
- Existing permission flows still initialize notifications after authorization from app startup, notification-allow screen, and My Page.
- Unit tests cover repeated
initialize() and repeated token refresh behavior.
Source: Codex codebase audit on 2026-06-28.
Problem
NotificationService.initialize()can be called from multiple user flows, but only local notification plugin setup is guarded. Repeated initialization can register additional FCM foreground/opened-app listeners and token-refresh listeners, causing duplicate notification handling, duplicate navigation, and repeated FCM token registration.Evidence
lib/core/services/notification_service.dartalways runs background handler registration, permission request,_setupMessageHandlers(), andrequestNotificationToken()frominitialize().setupFlutterNotifications()is guarded, but there is no equivalent guard for whole-service initialization, FCM message handlers, initial-message handling, or token-refresh subscription.requestNotificationToken()calls_messaging.onTokenRefresh.listen(...)every time it runs, without storing/canceling/reusing the subscription._setupMessageHandlers()callsFirebaseMessaging.onMessage.listen(...)andFirebaseMessaging.onMessageOpenedApp.listen(...)every time it runs, without storing/canceling/reusing subscriptions.initialize()calls do not create duplicate listeners or duplicate token registration attempts.Proposed direction
Make
NotificationService.initialize()idempotent at the service seam. Track initialization state and in-flight initialization, storeStreamSubscriptions for foreground, opened-app, and token refresh listeners, and ensure setup/listener registration runs once per service lifecycle.Keep
setupFlutterNotifications()idempotent, but do not rely on it as the only guard.Acceptance criteria
NotificationService.initialize()twice concurrently or sequentially registers FCM foreground, opened-app, and token-refresh listeners only once.getInitialMessage()more than intended or navigate twice for the same initial/opened message.initialize()and repeated token refresh behavior.Source: Codex codebase audit on 2026-06-28.