BowlerPlate
Features

Notifications

2/19/2026

Documentation for FCM, Local Notifications, and Notification Management.

The boilerplate provides a robust notification system that combines Firebase Cloud Messaging (FCM) for push notifications, Flutter Local Notifications for foreground alerts, and a dedicated Notification History for an in-app inbox.

Overview

Notifications in this project are handled through several layers:

  1. FCM (Firebase Cloud Messaging): Remote push notifications.
  2. Local Notifications: UI alerts shown when the app is in the foreground.
  3. Notification History: Backend-stored notification logs accessible via API.
  4. Settings Management: User preferences persisted locally and synced to the server.

🏗 Architecture

Notification Entry Points

The system is initialized in lib/main.dart within the main() function:

// main.dart
await initializeLocalNotification(
  onForegroundReceive: (response) => talker.debug(response.payload),
  notificationTapBackground: onBackgroundTap,
);

await initializeFCMNotification(
  onMessageReceivedBackground: _firebaseMessagingBackgroundHandler,
  onMessageReceived: (message) async {
    // Show local notification when FCM arrives in foreground
    showLocalNotification(message);
  },
  onTokenReceived: (token) => FcmService.updateTokenOnServer(token),
  onError: (err) => talker.error(err),
);

1. Firebase Cloud Messaging (FCM)

Uses the firebase_messaging package. The implementation is located in lib/helpers/notification.dart.

  • Token Management: FcmService handles gathering and refreshing FCM tokens. It automatically syncs the token to the backend when the user is authenticated.
  • Handling States:
    • Foreground: onMessage listener triggers a local notification.
    • Background/Terminated: onBackgroundMessage and getInitialMessage handle incoming data and navigation taps.
  • Permission Flow: Managed via NotificationConfig. By default, permissions are requested during the onboarding wizard for better conversion.

2. Local Notifications

Uses the flutter_local_notifications package for high-priority alerts when the app is active.

  • Initialization: Configures channels for Android (e.g., "Promotion") and settings for iOS (Alert/Badge/Sound).
  • Foreground Display: Because FCM doesn't show heads-up notifications when the app is in the foreground by default on Android, we manually trigger a local notification using the FCM message data.

3. Notification History (In-App Inbox)

The NotificationHistoryService provides methods to interact with backend-stored notifications:

  • Fetch: Paginated list of notifications.
  • Toggle State: Mark individual or all notifications as read.
  • Management: Delete individual or all notifications.

4. Settings & Preferences

Managed via Riverpod's NotificationSettingsNotifier.

  • Persistence: Preferences like "Push Enabled", "Vibration", and "Reminder 24h" are saved using SharedPreferences.
  • Server Sync: Critical settings (e.g., marketing promotions) are synced to the backend.
  • Offline Support: If a sync fails, it's queued for retry when the connection is restored or the app resumes.

🛠 Configuration

Notification Config

Modify lib/config/notification_config.dart to adjust behavior:

class NotificationConfig {
  static const String permissionRequestTiming = 'wizard'; // 'startup' or 'wizard'
  static const bool useProvisionalAuth = false;
  static const bool showExplanationFirst = true; // Show custom UI before system prompt
}

Firebase Setup

  1. Follow the standard Firebase setup for Flutter.
  2. Add google-services.json to android/app/.
  3. Add GoogleService-Info.plist to ios/Runner/.
  4. Ensure DefaultFirebaseOptions is generated in lib/firebase_options.dart.

🚀 Usage

Sending a Local Notification

To manually trigger a notification from code:

import 'package:mobile/helpers/notification.dart';

// Inside a function
await flutterLocalNotificationsPlugin.show(
  0,
  'Hello!',
  'This is a local notification',
  notificationDetails,
  payload: 'custom_data',
);

Accessing Notification Settings

Use the Riverpod provider to read or update settings:

final settings = ref.watch(notificationSettingsProvider);

// Update a setting
ref.read(notificationSettingsProvider.notifier).updateSetting(
  (s) => s.copyWith(promotions: true),
);

Fetching History

final historyService = ref.read(notificationHistoryServiceProvider);
final result = await historyService.getNotifications(page: 1);

🔗 Deep Linking

Notification taps are handled in onBackgroundMessageTapped. You can extend the logic to navigate to specific routes based on the RemoteMessage.data payload:

onBackgroundMessageTapped(
  handleMessage: (message) {
    if (message.data['type'] == 'order') {
       appRouter.push(OrderDetailsRoute(id: message.data['id']));
    }
  },
);

On this page