initialize method

Future<void> initialize({
  1. NotificationActionCallback? onNotificationTapped,
  2. NotificationButtonActionCallback? onNotificationAction,
  3. ForegroundMessageCallback? onForegroundMessage,
  4. NotificationErrorCallback? onError,
  5. bool showNotificationsInForeground = true,
  6. int reminderIntervalDays = 30,
  7. Future<void> onTokenChanged(
    1. String? newToken,
    2. String? oldToken
    )?,
  8. AuthServiceInt? authService,
  9. @Deprecated('Use authService parameter instead for race-free initialization') bool autoConnectAuth = true,
})

Initializes the notification service.

This method sets up all notification handling including:

  • Local notification plugin initialization
  • FCM message listeners (foreground, background, terminated)
  • Notification action handlers
  • Platform-specific configuration
  • Auto-wiring to auth service (if registered in GetIt)

This is the only method consuming apps need to call to set up notifications.

Parameters:

  • onNotificationTapped: Callback for when user taps a notification
  • onNotificationAction: Callback for when user taps an action button
  • onForegroundMessage: Callback for when notification arrives in foreground
  • onError: Callback for handling errors
  • showNotificationsInForeground: Whether to display notifications in foreground (default: true)
  • reminderIntervalDays: Days between permission reminders (default: 30)
  • onTokenChanged: Callback for FCM token changes. If not provided and auth service is available, uses the default Firebase callable function implementation.
  • authService: Pass auth service directly for race-free initialization. When provided, sets up auth connection immediately without using GetIt. This is the recommended approach for new code - see plan.auth-race.md.
  • autoConnectAuth: Whether to automatically connect to auth service if available in GetIt (default: true). Set to false if you want to call connectToAuthService manually with custom configuration. Deprecated: Use authService parameter instead.

For race-free initialization, pass authService directly and set autoConnectAuth: false:

await notificationService.initialize(
  authService: auth,
  autoConnectAuth: false,
  onNotificationTapped: (route, data) async { ... },
);

Legacy Initialization

await NotificationService().initialize(
  onNotificationTapped: (route, data) async {
    if (route != null) {
      Navigator.of(context).pushNamed(route, arguments: data);
    }
  },
  showNotificationsInForeground: true,
);

Implementation

Future<void> initialize({
  NotificationActionCallback? onNotificationTapped,
  NotificationButtonActionCallback? onNotificationAction,
  ForegroundMessageCallback? onForegroundMessage,
  NotificationErrorCallback? onError,
  bool showNotificationsInForeground = true,
  int reminderIntervalDays = 30,
  Future<void> Function(String? newToken, String? oldToken)? onTokenChanged,
  // NEW: Pass auth service directly for race-free initialization
  AuthServiceInt? authService,
  // DEPRECATED: Use authService parameter instead
  @Deprecated('Use authService parameter instead for race-free initialization')
  bool autoConnectAuth = true,
}) async {
  // CRITICAL: Set auth reference FIRST, before any await statements.
  //
  // The auth callback (handleAuthenticated) may fire immediately after
  // AuthService construction via a microtask. If we await anything before
  // setting _authService, the callback could execute while _authService
  // is still null.
  //
  // See: "Critical implementation constraints" section in plan.auth-race.md
  if (authService != null) {
    _authService = authService;
    _isConnectedToAuthService = true;
    logd('NotificationService: Initialized with auth service (race-free path)');
  }

  if (_initialized) {
    logi('NotificationService already initialized');
    return;
  }

  try {
    _onNotificationTapped = onNotificationTapped;
    _onNotificationAction = onNotificationAction;
    _onForegroundMessage = onForegroundMessage;
    _onError = onError;
    _showNotificationsInForeground = showNotificationsInForeground;
    _reminderIntervalDays = reminderIntervalDays;

    // Initialize local notifications plugin
    await _initializeLocalNotifications();

    // Set up FCM message handlers
    await _setupFCMHandlers();

    // Check for initial message (app opened from terminated state via notification)
    await _checkInitialMessage();

    _initialized = true;
    logi('NotificationService initialized successfully');

    // Store token callback if provided (used by both paths)
    if (onTokenChanged != null) {
      _onTokenChanged = onTokenChanged;
    }

    // Skip auto-connect if authService was provided directly (race-free path)
    if (authService != null) {
      logd('NotificationService: Auth service provided directly, skipping auto-connect');
      // Token callback already stored above, auth service already set at top
    } else if (autoConnectAuth) {
      // Legacy path: Auto-wire to auth service if available in GetIt
      // Check if auth service is available before attempting to connect
      bool authAvailable = false;
      try {
        authAvailable = GetIt.I.isRegistered<AuthServiceInt>();
      } catch (e) {
        // GetIt not initialized or other error
        logd('GetIt check failed: $e');
      }

      if (authAvailable) {
        await connectToAuthService(onTokenChanged: onTokenChanged);
      } else {
        // This is a configuration error - report it but don't crash
        const errorMsg = 'autoConnectAuth is enabled but AuthServiceInt is not '
            'registered in GetIt. FCM token management will not work automatically. '
            'Either register AuthServiceInt before initializing NotificationService, '
            'or set autoConnectAuth: false and call connectToAuthService() manually later.';
        loge(errorMsg);
        _onError?.call(errorMsg, null);
      }
    }
    // Note: If neither authService nor autoConnectAuth, token callback is still
    // stored above for manual connection later via connectToAuthService()
  } catch (e, stackTrace) {
    loge(e, 'Failed to initialize NotificationService', stackTrace);
    _onError?.call('Failed to initialize NotificationService: $e', stackTrace);
    rethrow;
  }
}