connectToAuthService method

Future<void> connectToAuthService({
  1. AuthServiceInt? authService,
  2. Future<void> onTokenChanged(
    1. String? newToken,
    2. String? oldToken
    )?,
})

Connects to an auth service to automatically manage FCM tokens on login/logout.

Note: This is the legacy initialization approach. For new code, prefer DreamicServices.initialize or passing authService directly to initialize for race-free setup.

This method may miss auth events on warm start (when user is already logged in) because callbacks are registered after Firebase listeners have already attached. See docs/plans/auth-race/plan.auth-race.md.

Login Behavior

When the user logs in:

Logout Behavior

When the user logs out:

  • Backend unregistration happens automatically via onAboutToLogOut callback (called before Firebase signOut while still authenticated)
  • Then performs local token cleanup (stops listener, deletes Firebase token, clears cache)
  • Manual preLogoutCleanup call is no longer needed for backend cleanup

DeviceService Integration

When DeviceServiceInt is registered in GetIt, token changes are automatically delegated to DeviceServiceInt.updateFcmToken. This ensures:

  • Single canonical device document per install/profile
  • Token stored alongside timezone and activity data
  • Token uniqueness enforcement across device docs

Recommended setup (race-free):

// Use DreamicServices for race-free initialization
final services = await DreamicServices.initialize(
  firebaseApp: Firebase.app(),
  enableDeviceService: true,
  enableNotifications: true,
);

Legacy setup (may miss auth events on warm start):

// 1. Register services in GetIt
GetIt.I.registerSingleton<DeviceServiceInt>(DeviceServiceImpl());

// 2. Connect DeviceService first (handles device doc lifecycle)
await deviceService.connectToAuthService();

// 3. Connect NotificationService (auto-delegates to DeviceService)
await notificationService.connectToAuthService();

If DeviceService is not registered, falls back to direct Firebase callable.

Parameters

authService is the auth service to connect to. If null, attempts to resolve from GetIt (guarded - logs and skips if not registered).

onTokenChanged callback for syncing tokens to your backend. If not provided and DeviceService is registered, uses DeviceService. Otherwise, uses the Firebase callable configured in AppConfigBase.notificationsUpdateFcmTokenFunction.

Example

// Standard setup (uses DeviceService if available)
await notificationService.connectToAuthService();

// Custom token handling
await notificationService.connectToAuthService(
  onTokenChanged: (newToken, oldToken) async {
    await myBackendService.updateFcmToken(newToken, oldToken);
  },
);

Implementation

Future<void> connectToAuthService({
  AuthServiceInt? authService,
  Future<void> Function(String? newToken, String? oldToken)? onTokenChanged,
}) async {
  // Cancel any existing subscription
  await _authSubscription?.cancel();

  // Store the token callback
  _onTokenChanged = onTokenChanged ?? _defaultTokenChangedCallback;

  // Try to get auth service
  AuthServiceInt? auth = authService;
  if (auth == null) {
    try {
      if (GetIt.I.isRegistered<AuthServiceInt>()) {
        auth = GetIt.I.get<AuthServiceInt>();
        logd('Resolved AuthServiceInt from GetIt');
      } else {
        logd('AuthServiceInt not registered in GetIt, skipping auth connection');
        return;
      }
    } catch (e) {
      logd('Could not resolve AuthServiceInt from GetIt: $e');
      return;
    }
  }

  // Remove any previously registered callback
  if (_aboutToLogOutCallback != null && _authService != null) {
    _authService!.removeOnAboutToLogOutCallback(_aboutToLogOutCallback!);
  }

  // Store reference to auth service for cleanup
  _authService = auth;
  _isConnectedToAuthService = true;

  // Register onAboutToLogOut callback
  // This is called BEFORE Firebase signOut while still authenticated
  _aboutToLogOutCallback = () async {
    // When DeviceService is registered, it handles backend cleanup by deleting
    // the device doc via its own onAboutToLogOut callback. NotificationService
    // should not trigger a token persistence write that would race with deletion.
    if (GetIt.I.isRegistered<DeviceServiceInt>()) {
      logd('onAboutToLogOut: DeviceService registered, skipping token persistence '
          '(device doc will be deleted by DeviceService)');
      return;
    }

    // Fallback for apps without DeviceService integration - perform backend
    // token unregistration via the custom callback
    logd('onAboutToLogOut: Performing backend token unregistration');
    if (_onTokenChanged != null && _cachedFcmToken != null) {
      try {
        await _onTokenChanged!(null, _cachedFcmToken);
        logd('Successfully unregistered FCM token on backend before logout');
      } catch (e) {
        logw('Failed to unregister FCM token on backend: $e');
        // Continue with logout even if backend call fails
      }
    }
  };
  auth.addOnAboutToLogOutCallback(_aboutToLogOutCallback!);
  logd('Added onAboutToLogOut callback for backend token cleanup');

  // Subscribe to auth changes
  _authSubscription = auth.isLoggedInStream.listen((isLoggedIn) async {
    if (isLoggedIn) {
      await _handleLogin();
    } else {
      // Local cleanup only - backend unregistration already happened in
      // onAboutToLogOut callback (before Firebase signOut).
      logd('Auth logout detected, performing local token cleanup');
      try {
        // Stop token refresh listener
        await _tokenRefreshSubscription?.cancel();
        _tokenRefreshSubscription = null;

        // Delete FCM token from Firebase
        try {
          await FirebaseMessaging.instance.deleteToken();
          logd('Deleted FCM token from Firebase');
        } catch (e) {
          logw('Failed to delete FCM token from Firebase: $e');
        }

        // Clear cached tokens
        await clearFcmToken();
        logd('Local token cleanup completed');
      } catch (e) {
        // Swallow any unexpected errors - cleanup is best-effort
        logw('Unexpected error during auto logout cleanup: $e');
      }
    }
  });

  logi('Connected to auth service for FCM token management');
}