Created
February 8, 2025 09:05
-
-
Save LordOlumide/12397336665372d76e04613896850010 to your computer and use it in GitHub Desktop.
Everything you should need to implement firebase notifications using awesome notifications
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:awesome_notifications/awesome_notifications.dart'; | |
| abstract class AwesomeNotificationActions { | |
| /// Use this method to detect when a new notification or a schedule is created | |
| @pragma("vm:entry-point") | |
| static Future<void> onNotificationCreatedMethod( | |
| ReceivedNotification receivedNotification, | |
| ) async { | |
| // Your code goes here | |
| } | |
| /// Use this method to detect every time that a new notification is displayed | |
| @pragma("vm:entry-point") | |
| static Future<void> onNotificationDisplayedMethod( | |
| ReceivedNotification receivedNotification, | |
| ) async { | |
| // Your code goes here | |
| } | |
| /// Use this method to detect if the user dismissed a notification | |
| @pragma("vm:entry-point") | |
| static Future<void> onDismissActionReceivedMethod( | |
| ReceivedAction receivedAction, | |
| ) async { | |
| // Your code goes here | |
| } | |
| /// Use this method to detect when the user taps on a notification or action button | |
| @pragma("vm:entry-point") | |
| static Future<void> onActionReceivedMethod( | |
| ReceivedAction receivedAction, | |
| ) async { | |
| if (receivedAction.title != null) { | |
| AppRoute? routeToPushTo = | |
| getRouteBasedOnNotificationTitle(receivedAction.title!); | |
| if (routeToPushTo != null) { | |
| AppRouter.router.goNamed(routeToPushTo.name); | |
| } | |
| } | |
| return; | |
| } | |
| static AppRoute? getRouteBasedOnNotificationTitle(String title) { | |
| if (title == NotificationsManager.announcementTitle) { | |
| return AppRoute.announcements; | |
| } | |
| return null; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import * as admin from "firebase-admin"; | |
| import { logger } from "firebase-functions"; | |
| import { MessagingPayload } from "firebase-admin/lib/messaging/messaging-api"; | |
| import { onDocumentCreated } from "firebase-functions/v2/firestore"; | |
| import { onSchedule } from "firebase-functions/v2/scheduler"; | |
| admin.initializeApp(); | |
| const announcementNotificationTopic = "Announcement"; | |
| const announcementNotificationTitle = "New Announcement!"; | |
| const announcementNotificationBody = "Tap to see the latest updates"; | |
| const tokenCollectionKey = "token"; | |
| export const onNewAnnouncementCreated = onDocumentCreated( | |
| "announcements/{announcement}", | |
| async (event) => { | |
| try { | |
| const snapshot = event.data; | |
| if (!snapshot) { | |
| logger.log("onNewAnnouncementCreated triggered with no announcement"); | |
| return; | |
| } | |
| const notification: MessagingPayload = { | |
| notification: { | |
| title: announcementNotificationTitle, | |
| body: announcementNotificationBody, | |
| }, | |
| }; | |
| await admin | |
| .messaging() | |
| .sendToTopic(announcementNotificationTopic, notification) | |
| .then(() => logger.log("Announcement notification sent successfully")) | |
| .catch((error) => { | |
| logger.log(`Error sending announcement notification: ${error}.`); | |
| }); | |
| } catch (error) { | |
| logger.log( | |
| `The error that occurred in onNewAnnouncementCreated is: ${error}.` | |
| ); | |
| } | |
| } | |
| ); | |
| export const refreshTokensPeriodically = onSchedule( | |
| { | |
| schedule: "11 23 3 * *", | |
| timeZone: "Africa/Lagos", | |
| }, | |
| async (_) => { | |
| // 1000 milliseconds * 60 * 60 * 24 * 60 = 60 days | |
| const EXPIRATION_TIME = 1000 * 60 * 60 * 24 * 60; | |
| try { | |
| const tokenSnapshot = await admin | |
| .firestore() | |
| .collection(tokenCollectionKey) | |
| .get(); | |
| let count = 0; | |
| for (const doc of tokenSnapshot.docs) { | |
| logger.log("==========================================="); | |
| const timeLastAccessed = doc | |
| .data()["timeLastAccessed"].toDate() | |
| .valueOf(); | |
| const duration = Date.now() - timeLastAccessed; | |
| if (duration > EXPIRATION_TIME) { | |
| count++; | |
| await admin | |
| .messaging() | |
| .unsubscribeFromTopic( | |
| doc.data()["token"], | |
| announcementNotificationTopic | |
| ), | |
| await doc.ref.delete(); | |
| logger.log("DELETED: " + doc.id); | |
| } | |
| } | |
| logger.log("Total documents deleted: " + count); | |
| return; | |
| } catch (error) { | |
| logger.log(`The error that occurred is: ${error}.`); | |
| } | |
| } | |
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| enum FirebaseNotificationTopic { | |
| announcement('Announcement'); | |
| final String topic; | |
| const FirebaseNotificationTopic(this.topic); | |
| } | |
| class FirebaseNotifications { | |
| FirebaseNotifications._(); | |
| static final FirebaseNotifications _instance = FirebaseNotifications._(); | |
| factory FirebaseNotifications() => _instance; | |
| final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance; | |
| StreamSubscription<ConnectivityResult>? onlineStatusSubscription; | |
| @override | |
| Future<void> initialize() async { | |
| // This function runs only once the first time the user comes online | |
| if (locator<OnlineStatusManager>().isOnline) { | |
| await _runInitializationLogic(); | |
| } else { | |
| onlineStatusSubscription = locator<OnlineStatusManager>() | |
| .stream | |
| .listen((connectivityResult) async { | |
| if (connectivityResult != ConnectivityResult.none) { | |
| await _runInitializationLogic().onError((_, __) => null); | |
| await onlineStatusSubscription!.cancel(); | |
| } | |
| }); | |
| } | |
| await _initPushNotifications(); | |
| } | |
| Future<void> _runInitializationLogic() async { | |
| final String? fcmToken = await _firebaseMessaging.getToken(); | |
| DocumentReference tokenRef = | |
| FirebaseFirestore.instance.collection('token').doc(fcmToken); | |
| // No need to worry about deleting previous token on token refresh. | |
| // There is a backend function that removes stale tokens. | |
| await tokenRef.set({ | |
| 'token': fcmToken, | |
| 'timeLastAccessed': FieldValue.serverTimestamp(), | |
| }); | |
| // If the notification preferences in settings store is | |
| // empty (preferences not initialized) or it is true (token may have changed), | |
| // subscribe to topics and if it is false, unsubscribe from topic. | |
| if ((await _firebaseMessaging.getNotificationSettings()) | |
| .authorizationStatus != | |
| AuthorizationStatus.denied) { | |
| // Affirm whatever setting was previously stored. | |
| final bool? isSubscribedToAnnouncement = | |
| locator<SettingsStore>().getSubscribedToAnnouncement; | |
| if (isSubscribedToAnnouncement == null || | |
| isSubscribedToAnnouncement == true) { | |
| await subscribeToAnnouncement(); | |
| } else if (isSubscribedToAnnouncement == false) { | |
| await unsubscribeFromAnnouncement(); | |
| } | |
| } else { | |
| await unsubscribeFromAnnouncement(); | |
| } | |
| } | |
| Future<void> _initPushNotifications() async { | |
| await FirebaseMessaging.instance | |
| .setForegroundNotificationPresentationOptions( | |
| alert: true, // Required to display a heads up notification on iOS | |
| badge: true, | |
| sound: true, | |
| ); | |
| FirebaseMessaging.instance.getInitialMessage().then(handleMessage); | |
| FirebaseMessaging.onMessageOpenedApp.listen(handleMessage); | |
| FirebaseMessaging.onBackgroundMessage(_handleBackgroundMessage); | |
| FirebaseMessaging.onMessage.listen(_onHandleForegroundMessage); | |
| } | |
| @override | |
| Future<bool> requestPermission() async { | |
| return (await _firebaseMessaging.requestPermission()).authorizationStatus != | |
| AuthorizationStatus.denied; | |
| } | |
| @override | |
| Future<void> handleMessage(RemoteMessage? message) async { | |
| if (message == null) return; | |
| if (message.notification?.title != null) { | |
| AppRoute? routeToPushTo = | |
| AwesomeNotificationActions.getRouteBasedOnNotificationTitle( | |
| message.notification!.title!, | |
| ); | |
| if (routeToPushTo != null) { | |
| AppRouter.router.goNamed(routeToPushTo.name); | |
| } | |
| } | |
| return; | |
| } | |
| @override | |
| Future<void> subscribeToAnnouncement() async { | |
| await FirebaseMessaging.instance | |
| .subscribeToTopic(FirebaseNotificationTopic.announcement.topic); | |
| await locator<SettingsStore>().updateSubscribedToAnnouncement(true); | |
| } | |
| @override | |
| Future<void> unsubscribeFromAnnouncement() async { | |
| await FirebaseMessaging.instance | |
| .unsubscribeFromTopic(FirebaseNotificationTopic.announcement.topic); | |
| await locator<SettingsStore>().updateSubscribedToAnnouncement(false); | |
| } | |
| } | |
| /// Triggered when a notification is RECEIVED while the app is in the background | |
| @pragma('vm:entry-point') | |
| Future<void> _handleBackgroundMessage(RemoteMessage message) async {} | |
| /// Triggered when a notification is RECEIVED while the app is in the foreground | |
| Future<void> _onHandleForegroundMessage(RemoteMessage message) async { | |
| RemoteNotification? notification = message.notification; | |
| if (notification == null) return; | |
| LocalNotifications().createNewInstantNotification( | |
| notification.hashCode, | |
| notification.title, | |
| notification.body, | |
| NotificationsManager.firebaseChannelKey, | |
| ); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class LocalNotifications implements LocalNotificationsInterface { | |
| LocalNotifications._(); | |
| static final LocalNotifications _instance = LocalNotifications._(); | |
| factory LocalNotifications() => _instance; | |
| @override | |
| Future<void> initialize() async { | |
| Workmanager().initialize(callbackDispatcher); | |
| AwesomeNotifications().initialize( | |
| 'resource://drawable/notification_icon', | |
| [ | |
| NotificationChannel( | |
| channelKey: NotificationsManager.firebaseChannelKey, | |
| channelName: 'Firebase notifications channel', | |
| channelDescription: 'Notification channel for firebase notifications', | |
| importance: NotificationImportance.Max, | |
| channelShowBadge: true, | |
| ), | |
| ], | |
| ); | |
| } | |
| @override | |
| Future<bool> requestPermissions(BuildContext context) async { | |
| bool permissionGranted = false; | |
| await AwesomeNotifications() | |
| .isNotificationAllowed() | |
| .then((isAllowed) async { | |
| if (!isAllowed) { | |
| if (context.mounted) { | |
| await showDialog( | |
| context: context, | |
| builder: (context) { | |
| return const InfoDialog( | |
| title: 'Notifications permission', | |
| info: | |
| 'We need permission to notify you of updates and reminders.', | |
| ); | |
| }, | |
| ); | |
| } | |
| permissionGranted = | |
| await AwesomeNotifications().requestPermissionToSendNotifications( | |
| permissions: const [ | |
| NotificationPermission.Alert, | |
| NotificationPermission.Sound, | |
| NotificationPermission.Badge, | |
| NotificationPermission.Vibration, | |
| NotificationPermission.Light, | |
| NotificationPermission.PreciseAlarms, | |
| ], | |
| ); | |
| } else { | |
| permissionGranted = true; | |
| } | |
| }); | |
| return permissionGranted; | |
| } | |
| @override | |
| Future<void> createNewInstantNotification( | |
| int id, | |
| String? title, | |
| String? body, | |
| String channel, | |
| ) async { | |
| AwesomeNotifications().createNotification( | |
| content: NotificationContent( | |
| id: id, | |
| channelKey: channel, | |
| actionType: ActionType.Default, | |
| title: title, | |
| body: body, | |
| category: NotificationCategory.Reminder, | |
| wakeUpScreen: true, | |
| ), | |
| ); | |
| } | |
| Future<void> _cancelAllLocalNotifications() async { | |
| await AwesomeNotifications().cancelAll(); | |
| await Workmanager().cancelAll(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:awesome_notifications/awesome_notifications.dart'; | |
| import 'package:my_app/src/entities/app/ui/notifications/local/local_notifications_interface.dart'; | |
| import 'package:my_app/src/entities/app/ui/notifications/remote/remote_notifications_interface.dart'; | |
| import 'package:my_app/src/entities/app/ui/routing/app_router.dart'; | |
| import 'package:my_app/src/entities/app/ui/routing/model/app_route.dart'; | |
| import 'package:flutter/material.dart'; | |
| part './awesome_notification_actions.dart'; | |
| abstract class NotificationsManager { | |
| static final LocalNotificationsInterface _local = LocalNotifications(); | |
| static final RemoteNotificationInterface _remote = FirebaseNotifications(); | |
| static const dailyNotificationsChannelKey = 'Daily_notifications_channel'; | |
| static const firebaseChannelKey = 'firebase_notifications_channel'; | |
| static const String announcementTitle = 'New Announcement!'; | |
| static Future<void> initialize() async { | |
| await _local.initialize(); | |
| await _remote.initialize(); | |
| } | |
| static Future<bool> requestPermission(BuildContext context) async { | |
| return (await _local.requestPermissions(context) && | |
| await _remote.requestPermission()); | |
| } | |
| static Future<void> subscribeToTopic(FirebaseNotificationTopic topic) async { | |
| switch (topic) { | |
| case FirebaseNotificationTopic.announcement: | |
| await _remote.subscribeToAnnouncement(); | |
| break; | |
| } | |
| } | |
| static Future<void> unsubscribeFromTopic( | |
| FirebaseNotificationTopic topic, | |
| ) async { | |
| switch (topic) { | |
| case FirebaseNotificationTopic.announcement: | |
| await _remote.unsubscribeFromAnnouncement(); | |
| break; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'dart:async'; | |
| import 'package:connectivity_plus/connectivity_plus.dart'; | |
| import 'package:flutter_bloc/flutter_bloc.dart'; | |
| class OnlineStatusManager extends Cubit<ConnectivityResult> { | |
| OnlineStatusManager() : super(ConnectivityResult.none) { | |
| _statusSubscription = Connectivity() | |
| .onConnectivityChanged | |
| .listen((ConnectivityResult result) { | |
| emit(result); | |
| }); | |
| } | |
| bool get isOnline => state != ConnectivityResult.none; | |
| late final StreamSubscription _statusSubscription; | |
| @override | |
| Future<void> close() async { | |
| await _statusSubscription.cancel(); | |
| return super.close(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment