FCM Push Notifications for Flutter

aditya nandedkar
6 min readNov 30, 2020
codesundar.com

This is my very first technical blog, so please spare incase of any mistakes

When used correctly, push notifications can be an excellent way to drive user engagement and keep your app in focus on a consistent basis. When used incorrectly, they can be annoying and motivate users to simply uninstall your app and never look back. Fortunately, Firebase Cloud Messaging (FCM) provides a sophisticated set of tools to send notifications only to users who actually want them. The following lesson will teach you how to configure FCM in Flutter.

After experiencing multiple issue while implementing and with lot of reference i have come up with a solution(There can be something better than this) and have already used in multiple projects

Before getting started, it is important to understand that there are three types of FCM push notifications you can send to a device.

  1. Device Token. Sends a message to a single device.
  2. Topic Subscription. Sends a message to multiple devices that explicitly subscribed to a topic.
  3. User Segment. Sends a notification to a subset of users based on your analytics data.

Step 0: Creating a Firebase Project and integrating it with Flutter Project

Android Setup

First, you need to decide on a project ID for your app using the following pattern <com>.<brand>.<app>. For example, the app for Fireship would be com.example.example . Create your app from the Firebase console.Step 1: Initial Setup

SHA1 Certificate (Optional)

An SHA1 certificate identifies your local machine allowing you use certain Firebase features, like Google Sign-In and Phone Auth.

Download and save the google-services.json

Next, go to the Firebase Console and register your app by clicking Add Firebase to your app Android. Enter your project ID and SHA1 certificate from the previous step.

Download the google-services.json file to the android/app directory. At this point, you can skip all remaining steps in the Firebase console (Flutter does this stuff automatically).

Update the build.gradle files

Now we need to register our Google services in the Gradle build files.

android/build.gradle

buildscript {
dependencies {
// ...
classpath 'com.google.gms:google-services:4.3.3' // <-- here
}
}

Next, update your project ID and register the Google services plugin at the bottom of gradle build file in the app directory.In addition to FCM, we will also install Cloud Firestore and Firebase Auth to build a full-stack push notification service.

android/app/build.gradle

apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services' // <-- add this line

// ...

defaultConfig {
applicationId "io.fireship.lessonapp" // <-- update this line
minSdkVersion 21 // <-- you might also need to change this to 21
multiDexEnabled true // <-- optional, but recommended
}

// ...

dependencies {
implementation 'com.android.support:multidex:1.0.3'
}

That’s it. Try executing flutter run with an Android device emulated or plugged-in to verify the setup worked.

iOS Setup

The iOS setup is less tedious and can be completed in one step.

Register and Download the GoogleService-Info.plist

Click add your app to iOS then download the GoogleService-Info.plist. Open the ios/runner.xcworkspace directory with Xcode, then drag the plist file into the Runner/Runner directory.

Signing Certificate

You must have a valid iOS Signing Certificate from your Apple Developer Account. If not, you will receive an error of No valid code signing certificates were found. Follow the steps below:

  • Log in with your Apple ID in Xcode first
  • Ensure you have a valid unique Bundle ID
  • Register your device with your Apple Developer Account
  • Let Xcode automatically provision a profile for your app
  • Rebuild your Project

That’s it we are done with firebase flutter setup, you can directly start using firebase services

Step 1: Initial Setup

Add Following dependencies in pubspec.yaml file

pubspec.yaml
dependencies:
flutter:
sdk: flutter

firebase_core: ^0.4.0
firebase_auth: ^0.11.1+6
cloud_firestore: ^0.12.5

firebase_messaging: ^5.0.2
get_it: null

ignore get_it plugin for now, we will see about it in the end part

Android

Android does not require any specific configuration, unless you want to run code after a notification is clicked-on from the device tray (while the app was in the background).

Add Following code in app level Manifest file

android/app/src/main/AndroidManifest.xml<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

iOS

iOS apps are required to generate a certificate for the Apple Push Notification service (APNs) and enable background services in Xcode. Rather than duplicate the content from the documentation, I recommend following the official setup guide from Firebase.

Step 2: Receiving Notification and Handling Click in App

I have created a Singelton wherein i initialise a Push notification service

class PushNotificationService {
final FirebaseMessaging _fcm = FirebaseMessaging();

Future initialise() async {
print(_pushRegisterService.isForeground);

if (Platform.isIOS) {
_fcm.requestNotificationPermissions(IosNotificationSettings());
}

_fcm.configure(
// Foreground
onMessage: (Map<String, dynamic> message) async {

foregroundSerializeAndNavigate(message);
},
// Application is closed
onLaunch: (Map<String, dynamic> message) async {
//Perform click action
},
// Background
onResume: (Map<String, dynamic> message) async {
print('onResume: $message');
//Perform click action
});
}

void foregroundSerializeAndNavigate(Map<String, dynamic> message) {
//TODO- Handle Click action here, entire notification body is //received in message
}
}
Core.dart Singeltonclass Core implements SessionInterface { PushNotificationService _pushNotificationService = PushNotificationService(); factory Core({onInitComplete}) {
Core._internal(onInitComplete);
}
Core._internal(onInitComplete) {
_initServices();
}
void _initServices() async {
await _pushNotificationService.initialise();
}
}

Initialise core in Main.dart

void main() {
runApp(MyApp());
}

class MyApp extends StatefulWidget {
// This widget is the root of your application.
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
@override
void initState() {
// TODO: implement initState
Core().getIt.registerLazySingleton(() => NavigationService());
super.initState();
}

@override
Widget build(BuildContext context) {

Core();
return MaterialApp(
title: 'title',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
navigatorKey: Core().getIt<NavigationService>().navigatorKey,
home: ActivityHome(),
);
}
}

ignore navigationKey for now, we will see about it in the end part

While Testing i observed following code was working fine for Foreground and Background state but On Android it was not working as expected when the app was being terminated

Modified Core.dart for above implementation

Core.dart (Singelton)class Core implements SessionInterface {PushNotificationService _pushNotificationService =                 PushNotificationService();
static final Core _core= Core._internal();

factory PushRegisterService() {
return _pushRegisterService;
}

bool isForeground = true;
bool fromNotification = false;
var message;
final getIt = GetIt.instance;
factory Core({onInitComplete}) {
return _core;
}
Core._internal(onInitComplete) {
_initServices();
}
void _initServices() async {
await _pushNotificationService.initialise();
}
}

Added a foreground flag and a message variable (Showing its use in next step wherein i have modified PushNotificationService.dart)

class PushNotificationService {
final FirebaseMessaging _fcm = FirebaseMessaging();

Future initialise() async {
print(_pushRegisterService.isForeground);

if (Platform.isIOS) {
_fcm.requestNotificationPermissions(IosNotificationSettings());
}

_fcm.configure(
// Foreground
onMessage: (Map<String, dynamic> message) async { _pushRegisterService.message = message;
foregroundSerializeAndNavigate(message);
},
// Application is closed
onLaunch: (Map<String, dynamic> message) async {
//Perform click action
_pushRegisterService.fromNotification = true;
_pushRegisterService.message = message;
},
// Background
onResume: (Map<String, dynamic> message) async {
print('onResume: $message');
_pushRegisterService.message = message;
//Perform click action
});
}

void foregroundSerializeAndNavigate(Map<String, dynamic> message) {
//TODO- Handle Click action here, entire notification body is //received in message
}
}

My observation was whenever we receive a notification in terminated state app compulsory redirects to HomeActivity specified in Main.dart so in the above code i have used a flag which i will use in HomeActivity for performing click action whenever app is in terminated state

HomeActivity.dart

HomeActivity extends StateBase<ActivityHome> {

@override
initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
HomeActivity() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_buildComplete = true;
if (_coreInitialized) {
onWidgetInitialized();
}
});

core = Core(onInitComplete: () {
_coreInitialized = true;
if (_buildComplete) {
onWidgetInitialized();
}
});

if (core.isInitialized) {
_coreInitialized = true;
}
} onWidgetIntitialized() { PushRegisterService().fromNotification = false;
Map<String, dynamic> data;
// Handle click actions
}
}

You cannot perform navigation without without context in Flutter (There are different ways, which i find difficult), so i use a service called get_it(Already included it’s dependency in pubspec.yaml and also i have intializesd it in Main.dart above)

This you will have to use in PushNotificationService.dart while handling click action

Core().getIt<NavigationService>().navigateTo(/*Your widget*/);

Just a Note for the backend developer or if you are using Firebase messaging console

Don’t forget to add click_action: FLUTTER_NOTIFICATION_CLICK in the notification body incase of sending a notification from backend and in Additional options if you are using Firebase messaging console

And that’s it we are done with the FCM Notification for flutter , you are ready to receive notification in your flutter app 🎉

Show some love ❤️ if you find it useful !!

Thank You ! Keep Coding !

--

--

aditya nandedkar

A passionate Flutter developer learning new things daily !