Skip to content

Instantly share code, notes, and snippets.

@LordOlumide
Created February 8, 2025 09:05
Show Gist options
  • Select an option

  • Save LordOlumide/12397336665372d76e04613896850010 to your computer and use it in GitHub Desktop.

Select an option

Save LordOlumide/12397336665372d76e04613896850010 to your computer and use it in GitHub Desktop.
Everything you should need to implement firebase notifications using awesome notifications
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;
}
}
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}.`);
}
}
);
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,
);
}
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();
}
}
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;
}
}
}
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