Notifications
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:
- FCM (Firebase Cloud Messaging): Remote push notifications.
- Local Notifications: UI alerts shown when the app is in the foreground.
- Notification History: Backend-stored notification logs accessible via API.
- 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:
FcmServicehandles gathering and refreshing FCM tokens. It automatically syncs the token to the backend when the user is authenticated. - Handling States:
- Foreground:
onMessagelistener triggers a local notification. - Background/Terminated:
onBackgroundMessageandgetInitialMessagehandle incoming data and navigation taps.
- Foreground:
- 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
- Follow the standard Firebase setup for Flutter.
- Add
google-services.jsontoandroid/app/. - Add
GoogleService-Info.plisttoios/Runner/. - Ensure
DefaultFirebaseOptionsis generated inlib/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']));
}
},
);