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
});