FlutterFlow Push Notifications Setup without Firebase Auth — Low Code — Part 2
In this guide, I show how to implement the following features on your FlutterFlow app:
- Notification Badge 🔴
- Handle FCM token refresh
- Direct to a specific page when opening a notification
Note: If you are here to learn how to implement push notifications in FlutterFlow without using the Firebase Auth, go to Part 1 of this guide:
Notification Badge
iOS
In iOS, the notification badge is the small red circle on the app icon with a number usually chosen to represent the number of unread/unopened notifications. There is no pre-built logic between the notifications received or opened and the number. The number is entirely customizable with your logic.
In my opinion, in iOS, the best way to handle badge numbers is by using the push notification payload. Simply add the number of unread messages for a user in the payload:
message: {
token: fcmToken,
notification: {
title: notificationTitle,
body: notificationBody,
image: notificationPhotoURL,
},
apns: {
payload: {
aps: {
contentAvailable: true,
badge: unReadCount, // an integer for badge number
},
},
},
}
For this to work, you need to record the user's unread messages on your server and add that to every push notification you send. This is only possible if you send push notifications using a cloud/edge function and not from the client side.
Here is an example of how to implement this using Supabase and edge functions with a similar setup explained in this example: https://supabase.com/docs/guides/functions/examples/push-notifications?platform=fcm
Add a column called is_read
to the Notifications
table with the default value set to False
. When the user reads a notification on the app, set this field to True
. One way to do this, if you have a notification component for displaying notifications in a notification center in your app, add this action to “On Initialization” of that component:
On your edge function, when sending a notification to this user, filter and count all the unread notifications for this user and use that number for the badge:
const { count:unReadCount } = await supabase
.from('Notifications')
.select('*', { count: 'exact' })
.eq('to', payload.record.to)
.eq('is_read', false)
const res = await fetch(
`https://fcm.googleapis.com/v1/projects/${serviceAccount.project_id}/messages:send`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
message: {
token: fcmToken,
notification: {
title: notificationTitle,
body: notificationBody,
image: senderPhotoURL,
},
apns: {
payload: {
aps: {
contentAvailable: true,
badge: unReadCount, // this one here
},
},
},
data: {
unReadCount: String(unReadCount), // for Android
},
},
}),
}
As you notice above, I am also adding the unReadCount
to the message.data
, which is unnecessary for iOS, but I will be using that for Android.
Clear Badge
For this, we will be using the flutter_app_badger
package to create a custom action that will remove the badge whenever it is called:
import 'package:flutter_app_badger/flutter_app_badger.dart';
Future<void> removeBadge() async {
await FlutterAppBadger.removeBadge();
}
Remember to add flutter_app_badger: 1.5.0
to Pubspec Dependencies. Now, you can call this action to clear the badge, for example, when the user opens the notifications page.
Android
Notification badges on Android are not as straightforward and can behave differently for different brands; for example, Pixel phones don’t support count in the badge. On the other hand, Pixel phones show the plain badge automatically as the app receives notifications, but the above remove action won’t work, and the user has to remove it by clearing notifications natively.
That said, I have a semi-solution using the FCM on-message-received listener (foreground). This is a semi-solution for two reasons:
- It won’t work for messages received in the background (I can’t figure out how to
FirebaseMessaging.onBackgroundMessage
) within FlutterFlow since you can’t add actions with input argument tomain.dart
; comment if you have a workaround for this) - It works only for supported devices listed here.
Create the following custom action with firebase_messaging: ^14.7.10
and flutter_app_badger: ^1.5.0
Pubspec Dependencies:
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart';
Future<void> onMessageListnerFCM() async {
await FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
if (message.notification != null) {
int unReadCount = int.parse(message.data['unReadCount']);
FlutterAppBadger.updateBadgeCount(unReadCount);
}
});
}
Then, add it to your main.dart
as a Final Action.
Handle FCM token refresh
The official guide here states that FCM tokens can change in some scenarios. To ensure that you will catch those events and refresh the token on your server, I do as follows:
- Create an App State called
fcmTokenRefresh
with a default value ofFalse
. - Create a custom action called
onTokenRefreshFCM
with the following code. As you can see, I don’t cover the Web.
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
Future<void> onTokenRefreshFCM() async {
if (!kIsWeb) {
FirebaseMessaging.instance.onTokenRefresh.listen((String fcmToken) async {
FFAppState().fcmTokenRefresh = true;
});
}
}
- Add
onTokenRefreshFCM
as a Final Action tomain.dart
. - In your app, whenever/wherever you find it proper, check for App State
fcmTokenRefresh
, and ifTrue
, get the FCM token again and save it.
Direct to a specific page when opening a Notification
Firebase has a solution and a listener to catch the event when the app opens via a notification, and we can create a custom action and add it as a Final Action to main.dart
for this purpose. However, our limitation with FlutterFlow is that Actions are added to themain.dart
can’t have an argument or include BuildContext (needed to navigate to a page).
The workaround that I have is using Deep Links. For that:
- You need to have deep linking enabled and defined for your FlutterFlow project.
- Create a custom action as follows:
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:url_launcher/url_launcher.dart';
final Uri _url = Uri.parse('backlog://thebacklog.app/Notifications'); //add desired page's deep link address here
Future<void> onMessageOpenedAppFCM() async {
RemoteMessage? initialMessage =
await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
await launchUrl(_url); //When app opens from a terminated state
}
await FirebaseMessaging.onMessageOpenedApp
.listen((RemoteMessage message) async {
await launchUrl(_url); //When the app is in the background
});
}
- Add this custom action to
main.dart
as a final action.
- You can also modify the
_url
based on the message, for example, directing to a different page based on your message payload.