Skip to main content

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:

TopicWhen SubscribedPurpose
school_{id}LoginSchool-wide announcements
student_{id}_attendanceLoginDaily attendance alerts
student_{id}_resultLoginResult published
student_{id}_feeLoginFee 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_preferences for 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
});