Push Notifications in Flutter — 1

Akshat Sharma
Flutter Clan
Published in
7 min readJun 30, 2021

Push Notifications are an integral part of any application. It helps to alert users to the information which they have opted-in from the app and services. For push notifications, Flutter has an amazing companion in form of Firebase. I’ll be releasing a series of articles that will help you integrate this framework into your Flutter apps. In this article, we will do a general setup for our flutter app.

Let’s begin!

Create a new flutter project

flutter create --org com.aks your_app

To link our application with firebase we’ll have to add firebase_core. In your pubspec.yaml file add the following dependency.

firebase_core: ^1.3.0

Now go ahead and run the project. You’ll see the counter project up and running.

Setup Firebase Project

Create a new project on the firebase console.

Let’s first add an android app to this newly created firebase project. Copy your package identifier from android/app/src/main/AndroidManifest.xmlfile. Paste the copied identifier while registering a new Android app.

After registering the app, download the google-services.json file at the same level as your app’s build.gradle file.

Now make the necessary changes to your project’s build.gradle file and to your app’s build.gradle file as described in the next step.

Now let’s add a new iOS project.

After registering the newly created iOS app, download GoogleService-Info.plist at the same level as your info.plist file under Runner folder and add it to your project.

We do not need to make any changes on the native side for firebase to work, that will be handled by firebase_core dependency which we earlier added.

In your main.dart file add the following lines to initialize the flutter app.

WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();

Add Firebase Messaging Package

firebase_messaging package gives us all the functionality out of the box which we need to make everything work. Add this dependency in your pubspec.yaml file and run your project once to download all the necessary files.

firebase_messaging: ^10.0.2

Make sure your iOS deployment target is 11 or higher to make things work. Uncomment the line in your Podfile and set the platform to 11.

platform :ios, '11.0'

Adding Push Notifications capability

Go to the Signing & Capabilities section of your iOS app in Xcode and add Background Modes.

Now checkmark Background fetch and Remote notifications option.

All the project-specific steps are now done. Now we’ll be focussing on the dart side of code.

Push Notifications Manager

Firebase Messaging gives us few functions to work with for configuring the behavior of push notifications. Let’s create a class that will handle everything inside it. We want our manager only to be created once so that we can get the retrieved data from notifications everywhere. That means we’ll have to create a singleton class.

class PushNotificationsManager {
PushNotificationsManager._();

factory PushNotificationsManager() => _instance;

static final PushNotificationsManager _instance = PushNotificationsManager._();
}

Create a function to fetch the FCM token.

/// Function to ask user for push notification permissions and if provided, save FCM Token in persisted local storage.
void _setFCMToken() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;

/// requesting permission for [alert], [badge] & [sound]. Only for iOS
NotificationSettings settings = await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);

/// saving token only if user granted access.
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
String token = await messaging.getToken();
print('FirebaseMessaging token: $token');
}
}

An app can receive notifications in any of the following app states: Foreground, Background, and Terminated State. The functionality of notifications can differ on basis of the state the app currently is in. Therefore we need to handle this in our scenario. Let's create an enum for this use case.

enum AppState {
foreground,
background,
terminated,
}

Add a configure method to your manager to handle notifications tap behaviour.

/// Function to configure the functionality of displaying and tapping on notifications.
void _configure() async {
/// For iOS only, setting values to show the notification when the app is in foreground state.
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);

/// handler when notification arrives. This handler is executed only when notification arrives in foreground state.
/// For iOS, OS handles the displaying of notification
/// For Android, we push local notification
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
_showForegroundNotificationInAndroid(message);
});

/// handler when user taps on the notification.
/// For iOS, it gets executed when the app is in [foreground] / [background] state.
/// For Android, it gets executed when the app is in [background] state.
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
_handleNotification(message: message.data, appState: AppState.foreground);
});

/// If the app is launched from terminated state by tapping on a notification, [getInitialMessage] function will return the
/// [RemoteMessage] only once.
RemoteMessage initialMessage = await FirebaseMessaging.instance.getInitialMessage();

/// if [RemoteMessage] is not null, this means that the app is launched from terminated state by tapping on the notification.
if (initialMessage != null) {
_handleNotification(message: initialMessage.data, appState: AppState.terminated);
}
}

By default, on Apple devices notification messages are only shown when the application is in the background or terminated state. Calling the following method updates these options to allow customizing notification presentation behaviour whilst the application is in the foreground.

await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);

To have the same functionality on Android we have to do some customization which we will cover in the subsequent series therefore we have left this function empty.

void _showForegroundNotificationInAndroid(RemoteMessage message) async {}

Function callbacks which gets triggered in the background and terminated state calls _handleNotification function in which we will handle the tap behaviour basis of the state the app currently is in.

void _handleNotification({
Map<String, dynamic> message,
AppState appState,
}) async {
print('PushNotificationsManager: _handleNotification ${message.toString()} ${appState.toString()}');
}

In the end PushNotificationsManger will look like:

import 'package:firebase_messaging/firebase_messaging.dart';

enum AppState {
foreground,
background,
terminated,
}

class PushNotificationsManager {
PushNotificationsManager._();

factory PushNotificationsManager() => _instance;

static final PushNotificationsManager _instance = PushNotificationsManager._();

/// Function to setup up push notifications and its configurations
Future<void> init() async {
await _setFCMToken();
_configure();
}

/// Function to ask user for push notification permissions and if provided, save FCM Token in persisted local storage.
void _setFCMToken() async {
FirebaseMessaging messaging = FirebaseMessaging.instance;

/// requesting permission for [alert], [badge] & [sound]. Only for iOS
NotificationSettings settings = await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);

/// saving token only if user granted access.
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
String token = await messaging.getToken();
print('FirebaseMessaging token: $token');
}
}

/// Function to configure the functionality of displaying and tapping on notifications.
void _configure() async {
/// For iOS only, setting values to show the notification when the app is in foreground state.
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);

/// handler when notification arrives. This handler is executed only when notification arrives in foreground state.
/// For iOS, OS handles the displaying of notification
/// For Android, we push local notification
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
_showForegroundNotificationInAndroid(message);
});

/// handler when user taps on the notification.
/// For iOS, it gets executed when the app is in [foreground] / [background] state.
/// For Android, it gets executed when the app is in [background] state.
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
_handleNotification(message: message.data, appState: AppState.foreground);
});

/// If the app is launched from terminated state by tapping on a notification, [getInitialMessage] function will return the
/// [RemoteMessage] only once.
RemoteMessage initialMessage = await FirebaseMessaging.instance.getInitialMessage();

/// if [RemoteMessage] is not null, this means that the app is launched from terminated state by tapping on the notification.
if (initialMessage != null) {
_handleNotification(message: initialMessage.data, appState: AppState.terminated);
}
}

void _showForegroundNotificationInAndroid(RemoteMessage message) async {}

void _handleNotification({
Map<String, dynamic> message,
AppState appState,
}) async {
print('PushNotificationsManager: _handleNotification ${message.toString()} ${appState.toString()}');
}
}

Now call init method of your manager class in either Splash Screen or Home Page. For this project call this method in initState method of MyHomePage widget.

@override
void initState() {
PushNotificationsManager().init();
super.initState();
}

You can find the code here. Make sure to check out the article to test out push notifications on an iOS simulator.

Until next time!

--

--