Implementing Push Notifications in Flutter Apps

Yong Shean
8 min readJul 23, 2020

--

Photo by Jamie Street on Unsplash

Push notifications are great for driving user engagement and notifying users for updates. Firebase Cloud Messaging (FCM) is a free (yes, zero cost!) cross-platform messaging solution provided by Google. It allows you to send notifications to a customised group of users on a variety of platforms easily.

In this article I’m going to show you how to implement push notifications on both Android and iOS — if you are interested in only setting it up on one of the platforms, feel free to skip the platform-specific sections.

Pre-requisites:

  • Project setup and dependencies: Flutter SDK, version 1.17.5, channel stable. We are also going to use two packages from pub.dev — firebase_messaging and overlay_support.
  • Make sure you have set up and configured Firebase services in your project. If you have already set up before (e.g. using other Firebase services), you can skip the next section.
  • You will need to enable Firebase Cloud Messaging and perform the steps in the “Getting your project ready for FCM” section.
  • iOS only: Even if you have configured Firebase services before, you will still need to download and replace your GoogleService-Info.plist with the latest file generated by Firebase console.

Getting your project ready for Firebase services

Before you can add Firebase to your Android or iOS app, you need to create a Firebase project. After that, you can follow the steps below to set up for each platform.

Android:

  1. Using the Firebase Console add an Android app to your project: Download the generated google-services.json file and place it inside android/app.
  2. Add the google-services classpath to the [YOUR_PROJECT]/android/build.gradle file. The version numbers might be different from your current project setup.
dependencies {
// Example existing classpath
classpath 'com.android.tools.build:gradle:3.5.3'
// Add the google services classpath
classpath 'com.google.gms:google-services:4.3.2'
}

3. Add the apply plugin to the [YOUR_PROJECT]/android/app/build.gradle file.

// ADD THIS AT THE BOTTOM
apply plugin: 'com.google.gms.google-services'

iOS:

Using the Firebase Console add an iOS app to your project: Download the generated GoogleService-Info.plist file, open ios/Runner.xcworkspace with Xcode, and within Xcode place the file inside ios/Runner.

Getting your project ready for FCM

After you have set up your project for Firebase services, there are a few extra steps specific to setting up FCM listed below.

Android:

If you want the user to be notified in your app (via onResume and onLaunch, see below) when the user clicks on a notification in the system tray, include the following intent-filter within the <activity> tag of [YOUR_PROJECT]/android/app/src/main/AndroidManifest.xml:

<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

iOS:

  1. Generate the certificates required by Apple for receiving push notifications by following this guide in the Firebase docs. You can skip the section titled “Create the Provisioning Profile”.
  2. In Xcode, select Runner in the Project Navigator. In the Capabilities Tab turn on Push Notifications and Background Modes, and enable Background fetch and Remote notifications under Background Modes.
  3. Follow the steps in the “Upload your APNs certificate” section of the Firebase docs.

If you wish to make additional configurations, you may refer to the firebase_messaging documentation here.

Integrating FCM

In your Dart code, you need to import the plugin and instantiate it.

  1. Create a service that communicates with the Firebase messaging plugin.

2. Initialise the push notification service in your root widget.

Before we dive into showing the notifications, let’s figure out what’s inside the message received by the callbacks.

FCM messages

According to the official documentation, there are two types of messages you can send from FCM: notification messages and data messages.

Notification message

A notification message is displayed on the user’s device automatically by FCM without additional handling by the app developer. When a notification message is sent, the message will be displayed in the device tray when the app is in background.

Data message

Contrary to notification messages, a data message will not be displayed on the user’s device, unless processed with additional codes written by the app developer. This is useful for giving extra context to the app, for example, specifying the screen that opens after the user taps on the notification in the device tray. You can see an example in this tutorial.

Types of notifications

Background notifications

If the notification is sent when the app is closed, the notification will appear in the device tray with your app name, icon, and notification details. This notification is called a background notification because the notification is received in the background (the app is in the background or app is closed).

This depends on your user’s device settings, whether notifications are allowed — if they are not allowed, they will not show up in the device tray. In the code above, we call the requestNotificationPermissions method provided by Firebase messaging to show the dialog asking the user to allow notification in iOS devices (in Android it is already handled automatically).

Foreground notifications

If the notification is sent when the app is open, the notification will not appear in the device tray. Instead, we need to implement our notification widget in our app so that it is visible to the user.

Optionally, you may also handle onLaunch and onResume callbacks provided by Firebase messaging. onLaunch fires when the user opens the app from the state where the app is terminated, whereas onResume fires when the user opens the app from the state where the app is still running in the background.

Send a test message

There are two ways you can send a test message: via Firebase console or Postman (or any REST client you like).

Firebase console

The first method you can try is sending a test message from Firebase console (Cloud Messaging > New notification).

Compose notification page: looks like we need a token

Run your app on a device and look at your debug console to get the FCM registration token:

I/flutter (14316): FirebaseMessaging token: YOUR_DEVICE_TOKEN

This token uniquely identifies your device (note that it might change after you restart the device). After you have obtained the token, you can add the token into the text field and click Test.

Prepare to receive the message

We can create a class that contains our message content. Our notification message should contain a title and a body. Let’s do it:

class PushNotificationMessage {
final String title;
final String body;
PushNotificationMessage({
@required this.title,
@required this.body,
});
}

Android devices

Background notifications: Run your app but put your app in the background while you send the test message. You should be able to see the notification appear in your device tray. Same goes to when your app is closed or terminated.

Foreground notifications: Run your app and make sure your app is in the foreground when you send the test message. Look at your debug console and see if you see an output from the onMessage callback:

I/flutter (14316): onMessage: {notification: {title: Title of your notification, body: Body of your notification}, data: {}}

You can see that both title and body of the notification are inside the notification attribute — so in your code you should extract these out so we can show them.

onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
if (Platform.isAndroid) {
notification = PushNotificationMessage(
title: message['notification']['title'],
body: message['notification']['body'],
);
}
// show notification UI here
}

Check out the final section on how to show a custom notification UI.

iOS devices (physical devices only)

Since push notifications do not work on simulators (because, Apple 🤦‍♀), you should connect an iPhone and run the app.

Background notifications: The behaviour is identical to what you get on Android devices.

Foreground notifications: Run your app and make sure your app is in the foreground when you send the test message. Look at your debug console and see if you see an output from the onMessage callback:

I/flutter (14316): onMessage: {google.c.sender.id: 503783979139, google.c.a.e: 1, aps: {alert: {title: Hello1, body: Body1}}, gcm.n.e: 1, google.c.a.c_id: 8648462793465478148, google.c.a.udt: 0, gcm.message_id: 1593659255235806, google.c.a.ts: 1593659255}

You can see that both title and body of the notification are inside the aps.alert attribute — so in your code you should extract these out so you can show them.

onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
if (Platform.isIOS) {
notification = PushNotificationMessage(
title: message['aps']['alert']['title'],
body: message['aps']['alert']['body'],
);
}
// show notification UI here
}

Postman

The second method is to send a message request in Postman.

Tips: You can also import my public collection directly into Postman.

We’re going to use the POST method to https://fcm.googleapis.com/fcm/send and set the headers like this:

Authorization: key=YOUR_FCM_SERVER_KEY
Content-Type: application/json

The FCM server key can be found under the Project credentials section at Firebase Console > Project Settings > Cloud Messaging.

And your request body should look like this:

{
"to" : "YOUR_DEVICE_TOKEN",
"collapse_key" : "New Message",
"priority": "high",
"notification" : {
"title": "Title of Your Notification",
"body" : "Body of Your Notification",
}
}

You can find the usage of each parameter here.

Android devices

Once you fire the POST request, look at your Flutter app debug console and see if you see an output from the onMessage callback:

I/flutter (14316): onMessage: {notification: {title: Title of Your Notification, body: Body of Your Notification}, data: {body: Title of Your Notification, title: Body of Your Notification}}

You can see that both the title and body of the notification are contained in the notification attribute.

onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
if (Platform.isAndroid) {
notification = PushNotificationMessage(
title: message['notification']['title'],
body: message['notification']['body'],
);
}
// show notification UI here
}

iOS devices (physical devices only)

Once you fire the POST request, look at your Flutter app debug console and see if you see an output from the onMessage callback:

I/flutter (14316): onMessage: {body: Body of Your Notification2, gcm.message_id: 1594117734104281, google.c.sender.id: 503783979139, google.c.a.e: 1, title: Title of Your Notification2, aps: {alert: {title: Title of Your Notification2, body: Body of Your Notification2}}}

You can see that both the title and body of the notification are contained in the notification attribute.

onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
if (Platform.isIOS) {
notification = PushNotificationMessage(
title: message['aps']['alert']['title'],
body: message['aps']['alert']['body'],
);
}
// show notification UI here
}

Note that in the above example I’m using the legacy HTTP API, you might want to use HTTP v1 API to send from your server in production settings, you can check out the advantages and how to migrate from legacy to v1 here.

In production settings, you should send the message from your backend server via Firebase Admin SDK — more details here. If you happen to use NodeJS as the backend, check out this awesome tutorial from step 2 onwards.

Custom UI for foreground notifications

For this we use overlay_support to generate a notification widget, then we can show the widget in the onMessage callback. In the code snippet below, I use a simple notification layout to show the notification message received from FCM. Check out here for a demo on different types of notification widgets from overlay_support.

onMessage: (Map<String, dynamic> message) async {
...
// import from overlay_support
showSimpleNotification(
Container(child: Text(notification.body)),
position: NotificationPosition.top,
);
}

Further reads

Here are some useful links for further information:

Do you use images in your push notifications? How often do you send push notifications to your users? Let me know in the comments:)

Until next time! 👋

--

--

Yong Shean

🇲🇾 | Google Developer Expert - Flutter & Dart. Code for living and hobby. Lots of ❤ for Flutter. Always thinking of how to make things clearer and cleaner.