FlutterFlow Push Notifications Setup without Firebase Auth — Low Code — Part 2

Ali Ayremlou
5 min readFeb 5, 2024

--

In this guide, I show how to implement the following features on your FlutterFlow app:

  1. Notification Badge 🔴
  2. Handle FCM token refresh
  3. 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:

  1. 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 to main.dart; comment if you have a workaround for this)
  2. 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 of False.
  • 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 to main.dart.
  • In your app, whenever/wherever you find it proper, check for App State fcmTokenRefresh, and if True, 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.
Example for the Backlog app
  • 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.

--

--