How to use Firebase Cloud Messaging in a NativeScript Angular Mobile App

An often critical part of mobile application development is the requirement of being able to receive notifications from a remote source, often referred to as push notifications. Previously I had explored how to use local notifications in a NativeScript application, but these are triggered based on a schedule within the application itself, rather than a remote party. Both Android and iOS devices can receive push notifications and there are many services that will help with this, one of the more popular being the Firebase Cloud Messaging (FCM) service.

In this article, we're going to see how to configure Firebase to send push notifications to Android and iOS applications built with NativeScript and Angular.

Before getting too far ahead of ourselves, there are a few things that should be noted. The first thing is that push notifications will not work in an iOS simulator as of Xcode 8.3 and they will not work in Genymotion. You can test push notifications in an Android emulator that has the available Google APIs. The second thing is that you'll need a Mac to generate certificates that are required for sending notifications to iOS devices.

Registering a New Mobile Application with Firebase Cloud Messaging

There are two pieces to the push notification puzzle. There is the development of an application that can receive messages and there is the configuration of a service that can send notifications. We're going to start by configuring Firebase to be able to send push notifications to our Android and iOS NativeScript application.

  1. The first step is to head over to the Firebase Cloud Console and create a new project.

    Firebase Cloud Messaging Step 1

  2. Give the project a name and select your country. After the project has been created, you'll be taken to the project dashboard. Going forward we're going to be spending our time in the notifications section.

    Firebase Cloud Messaging Step 2

  3. From the notifications section of the dashboard, we need to create a new Android application. The steps for Android and iOS will be different, but we'll be exploring both at some point.

    Firebase Cloud Messaging Step 3

  4. When choosing to create a new Android project, you should provide the package name that you're using or are planning to use. This will be defined when creating our NativeScript application.

    Firebase Cloud Messaging Step 4

  5. Once the Android project is created, you'll be prompted to download a google-services.json file. Go ahead and download it as we'll be using it when configuring the NativeScript project.

  6. At this point in time, the Android project has been configured. However, we plan to include iOS into the mix as well. From the dashboard settings, choose to add a new application.

    Firebase Cloud Messaging Step 5

  7. The new application, this time around, will be iOS. Like with Android, you'll need to define information about your iOS application.

    Firebase Cloud Messaging Step 6

    The bundle information will be later defined when we create the NativeScript project. Make sure that the package name that you chose for Android matches the bundle id that you're setting for iOS.

    Firebase Cloud Messaging Step 7

  8. Like with Android, you'll need to download a file for iOS. During the creation process you'll be asked to download a GoogleService-Info.plist file. Go ahead and download it as it will be used later.

  9. We're not done yet though. Now we need to generate a certificate for using Apple Push Notifications (APNs). This is a long painful process which I personally find to be a major turn off when it comes to mobile application development.

    The official Firebase documentation does a pretty good job at explaining the process, so I'll direct you to it. Once you've generated the .p12 certificate file, it can be uploaded via the settings of your Firebase project.

    Firebase Cloud Messaging Step 8

    You'll need a developer certificate and a production certificate.

With both Android and iOS configured for Firebase Cloud Messaging via the Firebase dashboard, we can finally start developing our application.

Creating a New NativeScript with Angular Project

We're going to assume you already have the NativeScript CLI installed and Android or iOS configured for development on your computer.

From the CLI, execute the following command:

tns create push-project --ng

The above command will create an Angular project, hence the --ng flag. For simplicity, this will be a single page application that registers for push notifications, and then receives them – nothing more nothing less.

NativeScript Push Notifications

The above animated image illustrates what we're aiming to accomplish. Using Postman, or any other API tool we can send a message. If the application is in the background, we will get a tray notification. If the application is in the foreground we will get an alert.

Installing the NativeScript Push Notification Plugin and its Dependencies

With the project created, we need to install the push notification plugin and the dependencies that we had downloaded from Firebase.

From the CLI, execute the following command to install the plugin:

tns plugin add nativescript-push-notifications

At one point in time, this plugin used Google Cloud Messaging (GCM), which has now become Firebase Cloud Messaging (FCM). The plugin documentation is a little dated at the moment. Brad Martin did a great write-up when the plugin used GCM, and a lot of it is the basis that formed this guide.

With the plugin installed, let's drop in the files that we downloaded. Place the google-services.json file in the project's app/App_Resources/Android directory and the GoogleService-Info.plist file in the project's app/App_Resources/iOS directory.

The project is ready to receive some code!

Removing the NativeScript with Angular Boilerplate Code

Because this will be a single page application, we're going to wipe out a lot of the boilerplate code that came with our project. Within the project, remove the app/item directory if it exists.

Open the project's app/app.routing.ts file and make it look like the following:

import { NgModule } from "@angular/core";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { Routes } from "@angular/router";

const routes: Routes = [];

@NgModule({
    imports: [NativeScriptRouterModule.forRoot(routes)],
    exports: [NativeScriptRouterModule]
})
export class AppRoutingModule { }

In the above code, we just removed the routes that existed in the array.

We need to visit the @NgModule block of the project's app/app.module.ts file, so open it and make it look like the following:

import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { AppRoutingModule } from "./app.routing";
import { AppComponent } from "./app.component";

@NgModule({
    bootstrap: [
        AppComponent
    ],
    imports: [
        NativeScriptModule,
        AppRoutingModule
    ],
    declarations: [
        AppComponent
    ],
    providers: [],
    schemas: [
        NO_ERRORS_SCHEMA
    ]
})
export class AppModule { }

Again, this is all cleanup. Feel free to leave it if you want, but I just felt it is more than we need for this particular example.

Registering and Listening for Messages

All logic for our single page application will exist in the project's app/app.component.ts file. Open it and include the following TypeScript code:

import { Component } from "@angular/core";
import * as PushNotifications from "nativescript-push-notifications";

@Component({
    selector: "ns-app",
    templateUrl: "app.component.html",
})
export class AppComponent {

    public constructor() {
        let settings = {
            senderID: "FIREBASE_SENDER_ID_HERE",
            badge: true,
            sound: true,
            alert: true,
            interactiveSettings: {
                actions: [{
                    identifier: 'READ_IDENTIFIER',
                    title: 'Read',
                    activationMode: "foreground",
                    destructive: false,
                    authenticationRequired: true
                }, {
                    identifier: 'CANCEL_IDENTIFIER',
                    title: 'Cancel',
                    activationMode: "foreground",
                    destructive: true,
                    authenticationRequired: true
                }],
                categories: [{
                    identifier: 'READ_CATEGORY',
                    actionsForDefaultContext: ['READ_IDENTIFIER', 'CANCEL_IDENTIFIER'],
                    actionsForMinimalContext: ['READ_IDENTIFIER', 'CANCEL_IDENTIFIER']
                }]
            },
            notificationCallbackIOS: data => {
                console.log("DATA: " + JSON.stringify(data));
            },
            notificationCallbackAndroid: (message, data, notification) => {
                console.log("MESSAGE: " + JSON.stringify(message));
                console.log("DATA: " + JSON.stringify(data));
                console.log("NOTIFICATION: " + JSON.stringify(notification));
            }
        };
        PushNotifications.register(settings, data => {
            console.log("REGISTRATION ID: " + JSON.stringify(data));
            PushNotifications.onMessageReceived(settings.notificationCallbackAndroid);
        }, error => {
            console.dump(error);
        });
    }

}

There is a lot happening in the above code, so we'll break some of it down.

After importing the plugin, we need to define the settings, all of which were taken directly from the plugin documentation. An important value here is the senderID which can be obtained from either of the two files that were downloaded from Firebase, or from within the Firebase settings.

Inside the settings we have two methods:

notificationCallbackIOS: data => {
    console.log("DATA: " + JSON.stringify(data));
},
notificationCallbackAndroid: (message, data, notification) => {
    console.log("MESSAGE: " + JSON.stringify(message));
    console.log("DATA: " + JSON.stringify(data));
    console.log("NOTIFICATION: " + JSON.stringify(notification));
}

Depending on the platform, these are callback methods that will be run when a notification is received. The data received will include the message title, the body and any optional key-value pairs.

These settings are applied via the registration function like so:

PushNotifications.register(settings, data => {
    console.log("REGISTRATION ID: " + JSON.stringify(data));
    PushNotifications.onMessageReceived(settings.notificationCallbackAndroid);
}, error => {
    console.dump(error);
});

After registration happens a really long hash value will be returned. This hash value is the device id and it is how messages are sent to this particular device. This value should be stored on your server for each device if you wish to have full control when sending out messages.

After registration, you'll need to configure the onMessageReceived listener to use the method defined in the settings.

This is where things can get a little weird. Under certain circumstances the plugin identifies messages as being received in the background or foreground states. What you think they mean may not be actually what happens. This could be a bug, or not.

When might this matter?

When you receive a notification and the user taps on it, you may want to perform a certain action with the data. For example, open a particular page, etc. Depending on the state, the data will be passed via the listener or via the Android lifecycle events.

If you wish to receive the data via the lifecycle events, you might add something like this to your project:

Application.on(Application.resumeEvent, args => {
    if(args.android) {
        let intent = (args.android).getIntent();
        let extras = intent.getExtras();
        if(extras) {
            console.log(extras.get("name"));
        }
    }
});

In the above code, if the resume event is triggered, the name key is obtained from the tap event. This name key is a fictional key that we're assuming I created during the message request. You can name it whatever you want, or have multiple keys.

Play around with the listeners and the lifecycle events to get your desired results.

Cleaning the Application UI

To prevent any errors, let's finish cleaning the application from all the boilerplate code that came with our template. Open the project's app/app.component.html file and include the following HTML markup:

<ActionBar title="{N} Push Notifications"></ActionBar>

Remember, it is a simple application, so that is all the UI we're going to have in this project.

Sending Push Notifications with Postman via the Firebase Cloud Messaging API

The Firebase dashboard offers a way to send notifications, but in most cases you're going to be using your own server. In fact, the dashboard doesn't perform correctly with the plugin as of now.

If you're using the dashboard to send messages, you'll notice that you receive JSON errors on Android.

W/System.err( 2830): org.json.JSONException: No value for message
W/System.err( 2830):    at org.json.JSONObject.get(JSONObject.java:389)
W/System.err( 2830):    at org.json.JSONObject.getString(JSONObject.java:550)

This is because the message payload doesn't arrive how the plugin expects. At the end of the day, this won't matter because you're using your own setup right?

Per the Firebase documentation, you can send messages with minimal effort. In Postman or whatever you wish to use to send HTTP requests, you'll create a payload that looks like the following:

{
    "data": {
        "title": "Woot Woot",
        "message": "another test",
        "name": "Nic Raboy"
    },
    "to" : "dccrUrOy824:APA91bERfO...bE6BcMcSry_hzHhs"
}

There are a list of reserved properties as mentioned here. However, remember I was using name in the code?  This is where I crafted it. It is just a random key-value pair that I can include with the message. The to property is the registration id for any device we wish to send to. Remember, you should be storing them on your server.

We will need to be authorized to send messages. In the headers of your POST request, make sure to have an Authorization header that includes key=AAAAXie_080...Kbc3mF or whatever API key exists in your Firebase dashboard.

Conclusion

You just saw how to send push notifications to a NativeScript with Angular application using the Firebase Cloud Messaging system. This is incredibly useful under too many scenarios to list. Just remember, there are a few things to take note of. Remember that Google Cloud Messaging has become Firebase Cloud Messaging. Remember that the dashboard method of sending messages doesn't quite work with the plugin as of right now and that you should take note of the application states on the device. All very minor things.

Header image courtesy of Google

Comments