Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Ionic Native With Firebase FCM Push Notifications

Episode 93 written by Jeff Delaney
full courses and content on fireship.io


Broadcasting push notifications is one of the most effective strategies for keeping a mobile app’s user base engaged and active. In this lesson, we will build a multi-device notification feature with Ionic Native and Firebase Cloud Messaging (FCM) from scratch. My goal is to give you a reusable provider that works on both iOS and Android, then show you how to generate notifications manually on the Firebase console or dynamically with Cloud Functions.

This lesson is geared towards native mobile apps (iOS, Android) with Ionic. If you’re building a web app, checkout my PWA Firebase Cloud Messaging lesson.

the basic sequence of events required for sending push notifications with firebase

The basic process for sending FCM notifications goes something like this:

  1. Save a user’s device tokens in Firestore.
  2. Run backend code in response to important database events with Cloud Functions.
  3. Use Firebase Cloud Messaging to broadcast notifications to a user’s devices.

Full source code for the Ionic FCM Demo.

FCM demo with Ionic and Android

Step 1 - Initial Setup

There are a few things we need in place before getting started with Ionic and Firebase FCM. I’m starting from a brand new app using the tabs template.

ionic start fcmDemo tabs
cd fcmDemo

Install Dependencies

For native mobile apps, we will use the Firebase Cordova Plugin

ionic cordova plugin add cordova-plugin-firebase
npm install --save @ionic-native/firebase

For PWA support and access to the Firestore Database we will install AngularFire2.

npm install --save angularfire2 firebase

Then make sure you have imported everything in the app.module.ts.

import { Firebase } from '@ionic-native/firebase';

import { AngularFireModule } from 'angularfire2';
import { AngularFirestoreModule } from 'angularfire2/firestore';

const firebase = {
// your firebase web config
}

@NgModule({
imports: [
// ...
AngularFireModule.initializeApp(firebase),
AngularFirestoreModule,
],
providers: [
// ...
Firebase,
FcmProvider,

]
})

Add your iOS and Android Credentials to the Project

At this point, you should have your app registered for iOS and Android in your Firebase project. If not, follow these Ionic Firebase native app setup instructions.

Download your config files for iOS and Android from the Firebase console, then save them in the root of your Ionic project.

Your Android google-services.json file is found here:

screenshot of where to find firebase credentials for ios and android apps

And here is the GoogleService-Info.plist config for iOS.

screenshot of where to find firebase credentials for ios and android apps

Deploying to iOS? You will also need to enable push notifications in Xcode and in the developer portal. I recommend checking out this setup guide if you get stuck.

enable push notifications in Xcode for iOS

Step 2 - Obtain Permission

Most of the heavy lifting will happen in our FcmService. It is responsible for (1) obtaining permission from the user, (2) sending the token to Firestore, and (3) listening to incoming messages when the app is open.

ionic generate provider fcm

First, let’s flesh it out with our dependencies.

// fcm.ts

import { Injectable } from '@angular/core';
import { Firebase } from '@ionic-native/firebase';
import { Platform } from 'ionic-angular';
import { AngularFirestore } from 'angularfire2/firestore';


@Injectable()
export class FcmProvider {

constructor(
public firebaseNative: Firebase,
public afs: AngularFirestore,
private platform: Platform
) {}

// Get permission from the user
async getToken() { }

// Save the token to firestore
private saveTokenToFirestore(token) {}

// Listen to incoming FCM messages
listenToNotifications() {}

}

Obtain the FCM Token

Our first hurdle is obtaining the FCM permission token for a given device. We need to handle this differently on each platform. Specifically, iOS requires us to specifically request permission in our code. This function returns a promise, so we can make it look pretty with async/await.

async getToken() {

let token;

if (this.platform.is('android')) {
token = await this.firebaseNative.getToken()
}

if (this.platform.is('ios')) {
token = await this.firebaseNative.getToken();
await this.firebaseNative.grantPermission();
}

return this.saveTokenToFirestore(token)
}

Save the Token in Firestore

A user can have many devices registered for notifications, so we need a document that preserves the relationship between user and device. This is as simple as adding a userId property to each token. Also, a token is just a string, so we can use it as the document ID to ensure that each token only has 1 document.

private saveTokenToFirestore(token) {
if (!token) return;

const devicesRef = this.afs.collection('devices')

const docData = {
token,
userId: 'testUser',
}

return devicesRef.doc(token).set(docData)
}

Listen to Incoming Messages

Push notifications are designed to work only when the app is the background. When the app is actively in use, it is the developer’s responsibility to notify the user.

listenToNotifications() {
return this.firebaseNative.onNotificationOpen()
}

Ionic has a built in Toast Component, so we can just subscribe to the messages and throw up a toast for a few seconds on each emitted notification.

Let’s delegate this code to our app.component.ts after the native device is ready.

// ...omitted
import { FcmProvider } from '../../providers/fcm/fcm';

import { ToastController } from 'ionic-angular';
import { Subject } from 'rxjs/Subject';
import { tap } from 'rxjs/operators';

@Component({
templateUrl: 'app.html'
})
export class MyApp {
rootPage:any = TabsPage;

constructor(platform: Platform, fcm: FcmProvider, toastCtrl: ToastController) {
platform.ready().then(() => {

// Get a FCM token
fcm.getToken()

// Listen to incoming messages
fcm.listenToNotifications().pipe(
tap(msg => {
// show a toast
const toast = toastCtrl.create({
message: msg.body,
duration: 3000
});
toast.present();
})
)
.subscribe()
}

});
}
}

Fixing a Cordova Android Emulator Bug

As of March 2018, there’s a bug in the cordova Android emulator code, but it can be fixed by updating a single line of code, which I learned form this Ionic forum response.

Open /platforms/android/cordova/lib/emulator.js and find this line:

var num = target.split(’(API level ‘)[1].replace(’)’, ‘’);

Then replace it with this line:

var num = target.match(/\d+/)[0];

Step 3 - Broadcast Messages

Now for the fun part! At this point, we have obtained permission to send out push notifications, but how do we broadcast them to our users? We have two main options - send them manually via the console, or send them dynamically via Cloud Functions.

Manually from the Firebase Console

If you want to micromanage your app, you can broadcast messages directly from the Firebase console without writing a single line of code.

Send push notifications manually from the Firebase console

The console is great, but most FCM situations will be handled programmatically via a Cloud Function.

Dynamically with an FCM Cloud Function for Ionic

Let’s assume we want to send a notification whenever a user gets a new subscriber. In Firestore, we keep track of this data relationship on the following document.

subscriptions
subscriberId: 'Doug'
userId: 'AwesomeCodeTutorials'

We need a Cloud Function that will listen to the onCreate event in this collection and send out notifications to all devices registered to the AwesomeCodeTutorials user.

Run the following command inside your Ionic project (make sure to use the TypeScript option).

firebase init functions
cd functions

The Cloud Function code is actually very simple. It performs the following steps:

  1. Query the devices collection and order by the userId
  2. Format the message content.
  3. Loop over the devices and use the messaging SDK to send out the notification payload.
import * as functions from 'firebase-functions';

import * as admin from 'firebase-admin';
admin.initializeApp();


exports.newSubscriberNotification = functions.firestore
.document('subscribers/{subscriptionId}')
.onCreate(async event => {

const data = event.after.data();

const userId = data.userId
const subscriber = data.subscriberId

// Notification content
const payload = {
notification: {
title: 'New Subscriber',
body: `${subscriber} is following your content!`,
icon: 'https://goo.gl/Fz9nrQ'
}
}

// ref to the device collection for the user
const db = admin.firestore()
const devicesRef = db.collection('devices').where('userId', '==', userId)


// get the user's tokens and send notifications
const devices = await devicesRef.get();

const tokens = [];

// send a notification to each device token
devices.forEach(result => {
const token = result.data().token;

tokens.push( token )
})

return admin.messaging().sendToDevice(tokens, payload)

});

Deploy the Function

Now deploy the function from the command line:

firebase deploy --only functions

You can verify that it works by creating a new document in the Firestore subscribers collection with a userId that has approved devices. In this demo the emulated devices are assigned to testUser. You should see the notification popup after creating a new document in this collection.

The End

You now have a basic process for collecting FCM tokens and broadcasting notifications from your backend code. There are many other possibilities to expand on from here, such as customer segment notifications, A/B testing, topic subscriptions and more. Let me know if you have questions in the comments or on Slack.