NotificationService (FCM + Local)
core/services/notification_service.dart
Overview
Singleton wrapping FirebaseMessaging + flutter_local_notifications. Handles: permission request, foreground listener (shows a local notification), background handler, token retrieval/refresh callback, terminated-state initial-message check, topic sub/unsub.
User Flow
- initialize() requests permission, registers background handler, subscribes to onMessage, sets up local notifications channel.
- Foreground messages with a notification payload are mirrored into a local notification via _showNotification.
- FirebaseMessaging.onTokenRefresh forwards to _onTokenRefresh callback registered by DioHelper.syncFcmTokenForAllRoles.
- Background handler is a top-level @pragma('vm:entry-point') function — must stay at file scope.
- deleteToken is invoked on logout (AuthStateNotifier).
Cases & Edge Cases
- Android creates a high-importance channel ('high_importance_channel') — required for heads-up display.
- iOS requires presentAlert/presentBadge/presentSound to be true at show time, otherwise the local notification is silent.
- Permission denial silently fails — initialize still completes; getToken may return null. UI shows no notifications without surfacing this to the user yet.
- Topic subscribe/unsubscribe is exposed but not used today.
Code References
- lib/src/core/services/notification_service.dart:8 // _firebaseMessagingBackgroundHandler
- lib/src/core/services/notification_service.dart:15 // NotificationService
- lib/src/core/services/notification_service.dart:33 // initialize
- lib/src/core/services/notification_service.dart:121 // _initLocalNotifications
- lib/src/core/services/notification_service.dart:180 // deleteToken
API Calls
Notes
Initialize is invoked from main.dart before runApp; flutter_local_notifications must initialize after Firebase to share channel IDs cleanly. Native SDK contracts (no HTTP — Firebase + local notifications plugin): • FirebaseMessaging.requestPermission(alert,badge,sound,…) → NotificationSettings { authorizationStatus } — screen: app boot (main.dart) • FirebaseMessaging.getToken() → String? (device FCM token) — screen: app boot + DioHelper._checkAndSendFcmToken • FirebaseMessaging.onTokenRefresh → Stream<String> — screen: app boot (wires into DioHelper.syncFcmTokenForAllRoles) • FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler) — top-level entry-point — screen: app boot (debug log only) • FirebaseMessaging.onMessage.listen → RemoteMessage { notification: { title, body }, data: {…} } — screen: app boot → _showNotification (mirrors to local notif) • FirebaseMessaging.onMessageOpenedApp / getInitialMessage() → RemoteMessage — screen: app boot (deep-link to /orders/:id, etc.) • flutter_local_notifications.show(id, title, body, NotificationDetails(...)) → void — channel 'high_importance_channel'.