Skip to content

Conversation

@AndreaDiazCorreia
Copy link
Member

@AndreaDiazCorreia AndreaDiazCorreia commented Dec 30, 2025

Implements Phase 2 of FCM integration - complete Firebase Cloud Messaging service with token management, permissions, and background message handling following MIP-05 privacy approach.

Changes

  • FCMService class - Token generation, storage, and refresh
  • Background handler - Wakes app to process new events
  • Permission handling - Request notification permissions
  • Platform detection - Skip Firebase on Linux
  • Riverpod provider - Dependency injection
  • Main.dart integration - Initialize FCM on startup
  • Minimal logging - Essential logs only

How to Test

1. Basic Test

fvm flutter run -d android

Expected: FCM: Initialized successfully in logs

2. Token Generation

Expected: FCM: Token obtained in logs

  • Verify token stored in SharedPreferences (fcm_token key)

3. Permissions

Expected: Permission dialog on first launch

  • Grant/deny - app should continue working

4. Background Handler

Expected: No crashes on push notification

  • Send test message from Firebase Console
  • App should not crash

5. Linux Compatibility

fvm flutter run -d linux

Expected: FCM: Skipping initialization on Linux

Success Criteria

✅ App launches without errors
✅ Token generated and stored
✅ Permissions requested
✅ No crashes on background messages
✅ Linux compatibility maintained

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Firebase Cloud Messaging integration with platform-aware initialization and background message handling, enabling push notification support.
    • Implemented automatic token management and permission handling for notifications.
  • Documentation

    • Completed Phase 2 of FCM implementation documentation.

✏️ Tip: You can customize this high-level summary in your review settings.

Add FCMService to handle Firebase Cloud Messaging integration following MIP-05 approach:
- Implement background message handler to wake app when notifications arrive
- Add FCM token management with automatic refresh handling
- Set up foreground and background message listeners
- Integrate with existing flutter_background_service for event processing
- Add platform checks to skip Firebase on unsupported platforms (Linux, web)
- Include
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 30, 2025

Walkthrough

Firebase Cloud Messaging integration is implemented with a new FCMService class handling initialization, token management, and message handling. Platform-aware logic added to main.dart startup sequence. Riverpod provider setup for dependency injection. Phase 2 documentation marked complete.

Changes

Cohort / File(s) Summary
Documentation
docs/FCM_IMPLEMENTATION.md
Phase 2 status updated from TO IMPLEMENT to COMPLETE; branch creation note removed.
FCM Service Core
lib/services/fcm_service.dart
New FCMService class with Firebase initialization, notification permission requests, token lifecycle management (get/delete with timeout), foreground/background message handlers, shared preferences persistence, and error handling. Includes top-level firebaseMessagingBackgroundHandler for background context.
Startup Integration
lib/main.dart
Platform-aware FCM initialization added to main startup sequence (skipped on Linux). Calls _initializeFirebaseMessaging() after background service setup with error handling to prevent crashes.
Provider Setup
lib/shared/providers/fcm_service_provider.dart,
lib/shared/providers/providers.dart
New Riverpod Provider for dependency injection via SharedPreferencesAsync. Export added to providers.dart public API.

Sequence Diagram(s)

sequenceDiagram
    participant App as App (main)
    participant FCMInit as _initializeFirebaseMessaging
    participant FCMService as FCMService
    participant Firebase as Firebase/FCM
    participant SharedPrefs as SharedPreferences
    participant BGService as Background Service

    App->>FCMInit: Platform check (skip Linux)
    FCMInit->>FCMService: create & initialize()
    FCMService->>Firebase: Initialize Firebase
    FCMService->>Firebase: Request notification permissions
    FCMService->>Firebase: Get FCM token
    Firebase-->>FCMService: token
    FCMService->>SharedPrefs: Store token
    FCMService->>Firebase: setOnTokenRefreshListener()
    FCMService->>Firebase: onMessage listener (foreground)
    FCMService->>Firebase: onBackgroundMessage handler
    rect rgb(200, 220, 255)
        Note over FCMService,BGService: Background message arrives
        Firebase->>BGService: firebaseMessagingBackgroundHandler
        BGService->>SharedPrefs: Record wake timestamp
        BGService->>BGService: Mark pending fetch if inactive
    end
    FCMService-->>App: Initialized successfully
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • #391 — Adds Firebase configuration and firebase_options.dart that the FCMService and main.dart initialization depend on.

Suggested reviewers

  • Catrya
  • grunch

Poem

🐰 A messaging rabbit hops with glee,
FCM tokens hop from device to tree,
Background whispers, foreground calls align,
Phase Two complete—notifications divine! 📬✨

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: implementing FCM service with background integration as Phase 2, matching the changeset which adds FCMService, background handlers, and initialization logic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
lib/services/fcm_service.dart (2)

190-216: Empty conditional block creates dead code.

Lines 204-207 contain an empty if (isRunning) block that does nothing. If this is intentional (relying on the background service's existing subscription mechanism), remove the block and add a comment. If future action is planned, add a TODO comment.

🔎 Suggested clarification
       if (isRunning) {
         // The background service will pick up new events through its
         // existing subscription mechanism
+        // No action needed here - background service handles fetching
       }

Or remove the empty block entirely:

       if (isRunning) {
-        // The background service will pick up new events through its
-        // existing subscription mechanism
       }
+      // Background service handles fetching via its existing subscription mechanism

173-188: Token refresh listener is properly implemented.

Canceling existing subscription before setup prevents duplicate listeners. The TODO for Phase 3 encrypted token registration is appropriately documented.

The TODO at line 181 indicates Phase 3 work. Do you want me to open an issue to track this?

lib/main.dart (2)

43-44: FCM initialization placement is correct, but instance is not managed.

The initialization timing after background service is appropriate. However, the FCMService instance created in _initializeFirebaseMessaging is discarded after initialization. Consider either:

  1. Using fcmServiceProvider with an override in the ProviderContainer for lifecycle management
  2. Storing the instance for proper disposal on app termination

Currently, the service's stream subscriptions won't be disposed when the app terminates.

🔎 Suggested approach using provider override
+  FCMService? fcmService;
   // Initialize FCM (skip on Linux)
-  await _initializeFirebaseMessaging(sharedPreferences);
+  if (!kIsWeb && !Platform.isLinux) {
+    fcmService = FCMService(sharedPreferences);
+    await fcmService.initialize();
+  }

   final container = ProviderContainer(
     overrides: [
       settingsProvider.overrideWith((b) => settings),
       backgroundServiceProvider.overrideWithValue(backgroundService),
+      if (fcmService != null) fcmServiceProvider.overrideWithValue(fcmService),
       // ... other overrides
     ],
   );

92-108: Duplicate platform check and orphaned service instance.

Two concerns:

  1. Duplicate platform check: Lines 96-100 duplicate the check already in FCMService.initialize() (line 81). While defensive, this adds maintenance burden.

  2. Orphaned instance: The FCMService created at line 102 goes out of scope after initialization, meaning dispose() is never called and stream subscriptions leak.

Consider removing the outer platform check (let FCMService handle it) and returning the instance for lifecycle management.

🔎 Suggested simplification
-/// Initialize Firebase Cloud Messaging
-Future<void> _initializeFirebaseMessaging(SharedPreferencesAsync prefs) async {
+/// Initialize Firebase Cloud Messaging and return the service for lifecycle management
+Future<FCMService?> _initializeFirebaseMessaging(SharedPreferencesAsync prefs) async {
   try {
-    // Skip Firebase initialization on Linux (not supported)
-    if (!kIsWeb && Platform.isLinux) {
-      debugPrint(
-          'Firebase not supported on Linux - skipping FCM initialization');
-      return;
-    }
-
     final fcmService = FCMService(prefs);
     await fcmService.initialize();
-    debugPrint('Firebase Cloud Messaging initialized successfully');
+    if (fcmService.isInitialized) {
+      debugPrint('Firebase Cloud Messaging initialized successfully');
+      return fcmService;
+    }
+    return null;
   } catch (e) {
-    // Log error but don't crash app if FCM initialization fails
     debugPrint('Failed to initialize Firebase Cloud Messaging: $e');
+    return null;
   }
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c743429 and 12a1d17.

📒 Files selected for processing (5)
  • docs/FCM_IMPLEMENTATION.md
  • lib/main.dart
  • lib/services/fcm_service.dart
  • lib/shared/providers/fcm_service_provider.dart
  • lib/shared/providers/providers.dart
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{dart,flutter}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{dart,flutter}: Run flutter analyze after any code change - Mandatory before commits to ensure zero linting issues
Run flutter test after any code change - Mandatory before commits to ensure all unit tests pass

Files:

  • lib/shared/providers/providers.dart
  • lib/shared/providers/fcm_service_provider.dart
  • lib/services/fcm_service.dart
  • lib/main.dart
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes
All code comments must be in English - use clear, concise English for variable names, function names, and comments
Always check mounted before using BuildContext after async operations to prevent errors on disposed widgets
Use const constructors where possible for better performance and immutability
Remove unused imports and dependencies to maintain code cleanliness and reduce build size

**/*.dart: Application code should be organized under lib/, grouped by domain with lib/features/<feature>/ structure, shared utilities in lib/shared/, dependency wiring in lib/core/, and services in lib/services/
Persistence, APIs, and background jobs should live in lib/data/ and lib/background/; generated localization output must be in lib/generated/ and must stay untouched
Apply flutter format . to enforce canonical Dart formatting (two-space indentation, trailing commas) before committing
Resolve every analyzer warning in Dart code
Name Riverpod providers using the <Feature>Provider or <Feature>Notifier convention
Localize all user-facing strings via ARB files and access them with S.of(context) rather than hard-coded literals

Files:

  • lib/shared/providers/providers.dart
  • lib/shared/providers/fcm_service_provider.dart
  • lib/services/fcm_service.dart
  • lib/main.dart
lib/shared/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Follow existing feature patterns when adding new shared utilities - refer to order, chat, and auth features as implementation examples

Files:

  • lib/shared/providers/providers.dart
  • lib/shared/providers/fcm_service_provider.dart
lib/services/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Access NostrService for all Nostr protocol interactions - NostrService manages relay connections and messaging

Files:

  • lib/services/fcm_service.dart
lib/main.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Configure timeago package locales in app initialization for proper relative time formatting (e.g., 'hace X horas' vs 'hours ago')

Files:

  • lib/main.dart
🧠 Learnings (18)
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, `eventStorageProvider` is exported from `package:mostro_mobile/shared/providers/mostro_service_provider.dart` and not from a separate `event_storage_provider.dart` file.

Applied to files:

  • lib/shared/providers/providers.dart
  • lib/shared/providers/fcm_service_provider.dart
  • lib/main.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/shared/providers/providers.dart
  • lib/shared/providers/fcm_service_provider.dart
  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/shared/**/*.dart : Follow existing feature patterns when adding new shared utilities - refer to order, chat, and auth features as implementation examples

Applied to files:

  • lib/shared/providers/providers.dart
  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/relays/**/*.dart : Use dual storage strategy: store Mostro/default relays in `settings.relays` and user relays in `settings.userRelays` with full JSON metadata via `toJson()`/`fromJson()`

Applied to files:

  • lib/shared/providers/providers.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Organize Riverpod providers by feature in `features/{feature}/providers/` using Notifier pattern for complex state logic

Applied to files:

  • lib/shared/providers/providers.dart
  • lib/shared/providers/fcm_service_provider.dart
  • lib/main.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Name Riverpod providers using the `<Feature>Provider` or `<Feature>Notifier` convention

Applied to files:

  • lib/shared/providers/fcm_service_provider.dart
  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes

Applied to files:

  • lib/shared/providers/fcm_service_provider.dart
  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.081Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.081Z
Learning: Run `flutter test integration_test/` only for significant changes affecting core services and main user flows

Applied to files:

  • lib/services/fcm_service.dart
  • lib/main.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Persistence, APIs, and background jobs should live in `lib/data/` and `lib/background/`; generated localization output must be in `lib/generated/` and must stay untouched

Applied to files:

  • lib/services/fcm_service.dart
📚 Learning: 2025-05-08T16:29:52.154Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/background/background.dart:74-77
Timestamp: 2025-05-08T16:29:52.154Z
Learning: In the Mostro Mobile background service architecture, events aren't stored by the background process. Instead, the background service only checks if events exist in the eventStore and sends notifications for new ones, while the foreground process is responsible for storing and processing events. This is an intentional design decision to separate concerns.

Applied to files:

  • lib/services/fcm_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/subscriptions/subscription_manager.dart : Use SubscriptionManager with `fireImmediately: false` during SessionNotifier initialization to prevent premature execution

Applied to files:

  • lib/services/fcm_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/services/nostr_service.dart : Manage all relay connections and Nostr messaging through NostrService - automatically reconnect when relay list updates

Applied to files:

  • lib/services/fcm_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Always check `mounted` before using BuildContext after async operations to prevent errors on disposed widgets

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/screens/**/*.dart : Use `S.of(context)!.yourKey` for all user-facing strings instead of hardcoded text

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/screens/**/*.dart : Keep UI code declarative and side-effect free - use post-frame callbacks for side effects like SnackBars/dialogs

Applied to files:

  • lib/main.dart
📚 Learning: 2025-08-15T01:37:12.243Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 270
File: lib/shared/widgets/order_filter.dart:133-135
Timestamp: 2025-08-15T01:37:12.243Z
Learning: The MostroP2P/mobile project requires Flutter >=3.27.0 as specified in pubspec.yaml, which supports the Color.withValues() method, so usage of withValues() throughout the codebase is valid and should not be flagged as a compatibility issue.

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/data/repositories/**/*.dart : Implement Repository pattern for all data access - all data operations must go through repository classes

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Use Notifier pattern instead of simple StateNotifier for complex state logic requiring business rule encapsulation

Applied to files:

  • lib/main.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (12)
lib/services/fcm_service.dart (7)

1-11: LGTM - Imports and logger setup are appropriate.

The imports cover all necessary dependencies for Firebase messaging, background service integration, logging, and shared preferences. Logger instance at module level is reasonable for a service file.


20-58: Background handler implementation is well-structured.

The handler correctly:

  • Uses @pragma('vm:entry-point') for AOT compilation
  • Skips unsupported platforms
  • Initializes Firebase in the isolated background context
  • Sets a pending fetch flag when background service isn't running

One observation: when isRunning is true (line 47), no action is taken. This appears intentional per the MIP-05 design (existing service handles it), but consider adding a brief comment clarifying this is expected behavior.


76-121: Initialization flow is correct with proper error handling.

The sequential setup (Firebase → permissions → token → listeners → background handler) follows FCM best practices. Error handling allows graceful degradation without crashing the app.

Note: Firebase.initializeApp() may be called multiple times (here and in firebaseMessagingBackgroundHandler), but Firebase SDK safely handles duplicate initialization.


123-146: Permission request implementation is solid.

Appropriate permission options requested, and handling both authorized and provisional status as granted is correct iOS behavior. Error handling returns false safely.


148-171: Token retrieval with timeout is well-implemented.

The 10-second timeout prevents indefinite hangs, and the fallback to null allows graceful degradation when token retrieval fails.


246-264: Robust token deletion with fallback cleanup.

The nested try-catch ensures local storage is cleaned up even if the Firebase token deletion fails. This prevents orphaned tokens in storage.


266-272: Dispose method properly cleans up resources.

Stream subscriptions are canceled and nulled, and _isInitialized is reset, allowing potential re-initialization if needed.

lib/shared/providers/providers.dart (1)

1-3: LGTM - Export follows existing barrel file pattern.

The new FCM service provider export aligns with the existing pattern of centralizing provider exports.

docs/FCM_IMPLEMENTATION.md (2)

107-107: Documentation accurately reflects Phase 2 completion.

Status update to "✅ COMPLETE" is consistent with the implementation delivered in this PR.


241-243: Phase 2 section appropriately updated.

Branch name and status changes align with the PR completing Phase 2 FCM service implementation.

lib/main.dart (1)

1-2: Imports are appropriate for the new FCM functionality.

dart:io for Platform checks and flutter/foundation.dart for kIsWeb are correctly added alongside the FCM service import.

Also applies to: 13-13

lib/shared/providers/fcm_service_provider.dart (1)

1-8: Remove unused provider or refactor to use it consistently.

This provider is exported in lib/shared/providers/providers.dart but is never actually used anywhere in the codebase. Meanwhile, main.dart creates and initializes FCMService directly instead of through the provider. Either remove fcmServiceProvider if it's not needed, or refactor main.dart and any components that need FCM to use the provider instead of creating instances directly. If kept, consider using a FutureProvider to handle initialization through the provider system, which aligns with the coding guideline that Riverpod should be used for all state management.

Likely an incorrect or invalid review comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants