Notifications
Push Notifications (FCM)
The app receives push notifications via Firebase Cloud Messaging (FCM). Notifications are displayed even when the app is in the background or terminated.
Topics
Parents are subscribed to school-scoped topics upon login:
| Topic | When Subscribed | Purpose |
|---|---|---|
school_{id} | Login | School-wide announcements |
student_{id}_attendance | Login | Daily attendance alerts |
student_{id}_result | Login | Result published |
student_{id}_fee | Login | Fee reminders |
// Subscribe on login
await FirebaseMessaging.instance.subscribeToTopic('school_$schoolId');
await FirebaseMessaging.instance.subscribeToTopic('student_$studentId');
Notification Payload
{
"notification": {
"title": "New Announcement",
"body": "Mid-term exams schedule released"
},
"data": {
"type": "ANNOUNCEMENT",
"id": "ann_123",
"studentId": "stu_456"
},
"android": {
"channel_id": "syncad_announcements"
},
"apns": {
"payload": {
"aps": {
"sound": "default",
"badge": 1
}
}
}
}
Handling Foreground Notifications
Foreground notifications are handled via onMessage (not shown in tray):
class NotificationService {
final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
void initialize() {
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
_showLocalNotification(message);
});
}
Future<void> _showLocalNotification(RemoteMessage message) async {
await _flutterLocalNotificationsPlugin.show(
message.hashCode,
message.notification!.title,
message.notification!.body,
NotificationDetails(
android: AndroidNotificationDetails('syncad_general', 'General', channelDescription: ''),
iOS: const DarwinNotificationDetails(presentAlert: true, presentBadge: true, presentSound: true),
),
);
}
}
Notification Tap Handling
When a notification is tapped, the app navigates to the relevant screen based on data.type:
// In main.dart — handle background notification taps
FirebaseMessaging.onMessageOpenedApp.listen((message) {
final data = message.data;
switch (data['type']) {
case 'ANNOUNCEMENT':
Navigator.pushNamed(context, '/announcements/${data['id']}');
break;
case 'ATTENDANCE':
Navigator.pushNamed(context, '/attendance');
break;
case 'RESULT':
Navigator.pushNamed(context, '/exams');
break;
case 'FEE':
Navigator.pushNamed(context, '/fees');
break;
}
});
In-App Notification History
A dedicated Notifications screen shows all received notifications (synced from the server's notification log):
// GET /parent/notification/delivered-notifications
// Response: { data: [{ id, title, body, type, readAt, createdAt }] }
final notificationsProvider = FutureProvider.autoDispose<List<Notification>>((ref) {
return ref.read(notificationRepositoryProvider).getNotifications();
});
- Unread count shown on the app's notification bell icon
- Mark individual or all as read:
POST /parent/notification/read - Notifications are also stored locally via
shared_preferencesfor offline access
Badge Count
On iOS, the app badge reflects unread notification count:
FirebaseMessaging.onMessage.listen((message) {
final unreadCount = await _notificationRepository.getUnreadCount();
FlutterLocalNotificationsPlugin()
..resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
?.show(0, '', '', null, payload: '');
// Update badge via iOS method channel
});