Skip to content

Make NotificationService initialization idempotent #532

Description

@jjoonleo

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcodex-readyCan be started independently by a Codex thread nowpriority: P2Medium priority production-readiness workproduction-readinessWork required before production releasetest

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions