Code a Real-Time App With NativeScript: Push Notifications

NativeScript is a framework for building cross-platform native mobile apps using XML, CSS, and JavaScript. In this series, we’re trying out some of the cool things you can do with a NativeScript app: geolocation and Google Maps integration, SQLite database, Firebase integration, and push notifications. Along the way, we’re building a fitness app with real-time capabilities that will use each of these features.

Code a Real-Time App With NativeScript: Push Notifications

In this tutorial, you’ll learn how easy it is to add push notifications to your NativeScript app with the Firebase Cloud Messaging Service.

What You’ll Be Creating

Picking up from the previous tutorial, you’ll be adding push notifications to the app. A notification will be triggered when the user breaks their current record or when one of their friends takes first place away from them.

Setting Up the Project

If you have followed the previous tutorial on Firebase, you can simply use the same project and build the features that we will be adding in this tutorial. Otherwise, you can create a new project and copy the starter files into your project’s app folder.

tns create fitApp --appid "com.yourname.fitApp"

After that, you also need to install the geolocation, Google Maps, SQLite and Firebase plugins:

tns plugin add nativescript-geolocation
tns plugin add nativescript-google-maps-sdk
tns plugin add nativescript-sqlite
tns plugin add nativescript-plugin-firebase

Once installed, you need to configure the Google Maps plugin. You can read the complete instructions on how to do this by reading the section on Installing the Google Maps Plugin in the earlier tutorial.

Next, install the fecha library for formatting dates:

npm install --save fecha

After that, you also need to configure the Firebase plugin. Be sure to read the following sections in the previous tutorial so you can get the app running:

  • Running the Project
  • Setting Up a Firebase App
  • Setting Up a Facebook App
  • Installing the Firebase Plugin
  • Configuring Facebook Integration

Since we’ve already set up the Firebase plugin in the previous post, there’s only a little work that needs to be done to set up push notifications.

First, you have to reconfigure the plugin by going inside the node_modules/nativescript-plugin-firebase directory and running npm run config. This time, select both Facebook authentication and Messaging.

Once that’s done, open the firebase.nativescript.json file in the root directory of your project, and make sure that messaging is set to true:

{
    "using_ios": false,
    "using_android": true,
    "remote_config": false,
    "messaging": true,
    "crash_reporting": false,
    "storage": false,
    "facebook_auth": true,
    "google_auth": false,
    "admob": false,
    "invites": false
}

Next, open app/App_Resources/Android/AndroidManifest.xml and add the following services inside the <application>. This enables the Firebase messaging service for the app:

<application ...>

<service android:name="org.nativescript.plugins.firebase.MyFirebaseInstanceIDService">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
    </intent-filter>
</service>
<service android:name="org.nativescript.plugins.firebase.MyFirebaseMessagingService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>

</application>

Running the Project

You can run the project by executing tns run android. But since this app will build on the geolocation functionality, I recommend that you use a GPS emulator for quickly setting and changing your location. You can read about how to do so in the section on Running the App in the earlier tutorial.

If you get any build errors, you can remove the platform and rerun the app:

tns platform remove android
tns run android

Setting Up Firebase Cloud Functions

You’ll be using Firebase Cloud Functions to create a server that will send out the push notifications. This Firebase feature is used to run back-end code whenever a specific event happens within Firebase features that you’re using—for example, if there’s a new data saved in the real-time database, or when there’s a newly added user via the Firebase auth service. For this app, you’ll be using HTTP Triggers to send push notifications when the mobile app makes a request to a specific endpoint.

To use Firebase Cloud Functions, you first need to install the firebase-tools package globally:

npm install -g firebase-tools

Next, create a new folder that will house the server code. This should be outside your app folder. Inside that folder, install the firebase-functions package:

npm install [email protected] --save

Once it’s installed, log in to Firebase by running firebase login. This opens a new browser tab that allows you to log in with your Google account. Go through the whole process and agree to all permissions being asked.

Once you’re logged in, you can now initialize Firebase functions for a specific Firebase project:

firebase init functions

This will ask you whether you want to set up a default project or not. Select the Firebase project that you created in the previous tutorial:

Code a Real-Time App With NativeScript: Push Notifications

Next, you will be asked if you want the dependencies installed. Say yes.

Once the dependencies have all been installed, you should see a firebase.json file and a functions folder inside the directory. The file that you’ll be working on is the functions/index.js file. Open that file and you’ll see the following:

const functions = require('firebase-functions');

// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
//  response.send("Hello from Firebase!");
// });

Uncomment the helloWorld function, and you’ll get to see HTTP triggers in action.

exports.helloWorld = functions.https.onRequest((request, response) => {
    response.send("Hello from Firebase!");
});

Run the following to deploy the function to the cloud:

firebase deploy --only functions

Once the deployment is complete, it should show you the URL where the function has been deployed:

Code a Real-Time App With NativeScript: Push Notifications

Access that URL from your browser to see the output “Hello from Firebase!”

Adding the Server Code

Now you’re ready to add the code for implementing push notifications. First, you’ll add the code for the server component, then the code for the app.

Open the functions/index.js file and empty its contents.

Creating the Firebase Function

Import the Firebase packages that you’ll need:

const functions = require('firebase-functions'); // for listening to http triggers
const admin = require('firebase-admin'); // for accessing the realtime database
admin.initializeApp(functions.config().firebase); // initialize the Firebase Admin SDK

Create the init_push function. Note that the HTTP trigger is called for any request method, so you have to filter for the request method that you want to process. In this case, we only want to process POST requests. We expect the app to submit the id, steps, and friend_ids as the request data.

exports.init_push = functions.https.onRequest((request, response) => {
    
    if(request.method == 'POST'){

        var id = request.body.id; // ID of the user who made the request (Firebase Auth ID)
        var steps = parseInt(request.body.steps); // latest steps, not recorded yet
        var friend_ids = request.body.friend_ids.split(','); 

        friend_ids.push(id); // also include the ID of the current user

        // next: add code for getting the user and friends data
    }

});

Getting the User and Friends Data

Next, query the Firebase database to check if the user ID exists. This serves as a way to secure the endpoint so not just anyone can trigger push notifications. (Of course, a real app should have much better back-end security, so that users can’t spoof their own data or the data of somebody else.)

If the user does exist, query the database again so it returns all the users. Note that Firebase does not currently provide a way to return records based on an array of IDs, so we’ll have to filter the relevant data ourselves:

admin.database().ref('/users')
    .orderByChild('id')
    .limitToFirst(1)
    .equalTo(id)
    .once('value').then(snapshot => {
    
    var user_data = snapshot.val();

    if(user_data){
        // get all users from the database
        admin.database().ref('/users')
            .once('value').then(snapshot => {
            // next: add code for getting the current user's data and their friends data
        });
    }
});

Next, loop through the results returned from Firebase and create a new array that houses the friends_data. Once this is done, sort the array according to the number of steps by each user. The one with the highest number of steps has the first index.

var friends_data = [];
var current_user_data = null;
var notification_data = {};
var has_notification = false;

var users = snapshot.val();
for(var key in users){
    var user_id = users[key].id;
    
    if(friend_ids.indexOf(user_id) != -1 && id != user_id){ // the current user's friends
        friends_data.push(users[key]);
    }else if(id == user_id){ // the current user
        current_user_data = users[key];
    }
}

// sort in descending order by the number of steps
var sorted_friends_data = friends_data.sort(function(a, b) {
    return b.steps - a.steps;
}); 

// next: add code for constructing the notification payload

Construct the Notification Payload

Now we’re ready to determine who will receive the notification and construct the notification payload. Who is in first place? Is it the current user or one of the user’s friends? Since the current user will also have broken their own record when they break the overall record of whoever’s in first place, we just need to check if that record has been broken.

if(steps > sorted_friends_data[0].steps){
    // notify friend who was overtaken
    var diff_steps = steps - sorted_friends_data[0].steps;
    notification_data = {
        payload: {
            title: 'One of your friends beat your record',
            body: 'Too bad, your friend ' + current_user_data.user_name + ' just overtook you by ' + diff_steps + ' steps'
        },
        device_token: sorted_friends_data[0].device_token
    };
    has_notification = true;
    
}else if(steps > current_user_data.steps){
    // notify current user
    var diff_steps = steps - current_user_data.steps;
    notification_data = {
        payload: {
            title: 'You beat your record!',
            body: 'Congrats! You beat your current record by ' + diff_steps + ' steps!' 
        },
        device_token: current_user_data.device_token
    };
    has_notification = true;
}

// next: add code for sending push notification

Sending the Notification

Finally, send out the notification:

if(has_notification){
    
    var payload = {
      notification: notification_data.payload
    };
    
    // send push notification
    admin.messaging().sendToDevice(notification_data.device_token, payload).then(function(res) {
        
        response.send(JSON.stringify({'has_notification': true})); // inform the app that a notification was sent
    })
    .catch(function(error) {
        response.send(JSON.stringify(error)); // send the push notification error to the app
    });

}else{
    response.send(JSON.stringify({'has_notification': false})); // inform the app that a notification was not sent
}

Updating the App Code

Earlier, you set up the app so that it was able to receive push notifications. This time, you’ll be adding code so that your app can process those push notifications and display them to the user.

Receiving Push Notifications

The first thing that you need to do in order to receive push notifications is to update the firebase.init() function to include a listener for receiving the device token:

onPushTokenReceivedCallback: function(token) {
    // temporarily save it to application settings until such time that 
    // the user logs in for the first time
    applicationSettings.setString('device_token', token);
},

This function only executes once, so you have to save the token locally using application settings. Later on, this will allow us to get the device token when the user logs in for the first time. If you still remember from the previous tutorial, we’re saving the user’s data to Firebase the first time they log in.

Next, you can add the listener for when notifications are received. This will display an alert box which uses the title and body of the message as the content:

onMessageReceivedCallback: function(message) {
    dialogs.alert({
        title: message.title,
        message: message.body,
        okButtonText: "ok"
    });
},

Saving the Device Token to Firebase

Firebase Cloud Messaging requires the device token when sending out a push notification to a specific device. Since we’re already using Firebase, we’ll just save the device token along with the user data. For that, you need to edit the code for saving the user’s data to include the device token that we got earlier:

if(firebase_result.value == null){ 

    var device_token = applicationSettings.getString('device_token');

    var user_data = {
        'uid': fb_result.uid,
        'user_name': fb_result.name,
        'profile_photo': fb_result.profileImageURL,
        'device_token': device_token 
    };

}

Triggering Push Notifications

Push Notifications are triggered when one of two things happens:

  • when the user breaks their current record
  • when one of the user’s friends breaks their record and goes to first place

The first one is easy, so there’s really no need for additional setup. But for the second one, you need to do a little work. First, you have to edit the code for when the auth state changes. Right after extracting the friend IDs from the Facebook result, you have to save the friend IDs using application settings.

// extracting the friend IDs from the Facebook result
var friends_ids = r.data.map(function(obj){
    return obj.id;
});

// save the friend IDs
applicationSettings.setString('friends_ids', JSON.stringify(friends_ids));

friends_ids.push(user[user_key].id);

Next, update the code for when the user stops tracking their walk. Right after the code for constructing the user data for updating the user, get the friend IDs from application settings and include it in the object which contains the request data for triggering the push notification.

// construct the user data for updating the user's distance and steps
var user_key = applicationSettings.getString('user_key');
var user = applicationSettings.getString('user');
var user_data = JSON.parse(user);
user_data[user_key].distance = total_distance;
user_data[user_key].steps = total_steps;

// get friend IDs
var friends_ids = JSON.parse(applicationSettings.getString('friends_ids'));

var request_data = {
    'id': user_data[user_key].id,
    'friend_ids': friends_ids.join(','),
    'steps': total_steps
};

Make the request to the Firebase Cloud Functions endpoint that you created earlier. Once a success response is returned, only then will the user’s data be updated on the Firebase database.

http.request({ 
    url: "https://us-central1-pushapp-ab621.cloudfunctions.net/init_push", 
    method: "POST",
    headers: { "Content-Type": "application/json" },
    content: JSON.stringify(request_data) 
}).then(function (response) {
   
    var statusCode = response.statusCode;
    if(statusCode == 200){
        // update the user's data on Firebase
        firebase.update(
            '/users',
            user_data
        ); 

    }

}, function (e) {
    console.log('Error occurred while initiating push: ', e);
});

Testing Push Notifications

You can test the sending of push notifications by first uninstalling the app from the emulator or device. This allows us to properly trigger the function for getting the device token. Be sure to add console.log to output the device token:

onPushTokenReceivedCallback: function(token) {
    applicationSettings.setString('device_token', token);
    console.log('device token: ', device_token); // <-- add this
}

When the device token is outputted in the NativeScript console, copy it, click on the Database menu on your Firebase app dashboard, and add it as a device token to all the users of the app. Use device_token as the property name.

To trigger the push notification, you can use curl to make a POST request to the Firebase Function endpoint:

curl -X POST -H "Content-Type:application/json" YOUR_FIREBASE_FUNCTION_ENDPOINT -d '{"id":"ID OF A FIREBASE USER", "steps":NUMBER_OF_STEPS, "friend_ids":"COMMA,SEPARATED,FIREBASE,USER_IDs"}'

If you don’t have curl installed, you can use the Postman App to send the request. Use the following settings for the request:

  • Request method: POST
  • URL: Your Firebase function endpoint
  • Headers Key: Content-type
  • Headers Value: application/json
  • Body:
{"id":"ID OF A FIREBASE USER", "steps":NUMBER_OF_STEPS, "friend_ids":"COMMA,SEPARATED,FIREBASE,USER_IDs"}

Once triggered, you’ll see an output similar to the following:

Code a Real-Time App With NativeScript: Push Notifications

If the app isn’t currently open, you’ll see the notification in the notification area:

Code a Real-Time App With NativeScript: Push Notifications

Conclusion

Congratulations! You’ve finally completed the fitness app. Over the course of four tutorials, you’ve built a NativeScript app which uses Google maps, SQLite, Firebase Realtime database, and Firebase Cloud Messaging. Now you have a pretty good foundation for building NativeScript apps which use those technologies.

To learn more about NativeScript or other cross-platform mobile technologies, be sure to check out some of our other courses and tutorials here on ThemeKeeper Tuts+!

  • Code a Real-Time App With NativeScript: Push Notifications
    NativeScript
    Code a Mobile App With NativeScript
    Keyvan Kasaei
  • Code a Real-Time App With NativeScript: Push Notifications
    NativeScript
    Get Started With NativeScript and Mobile Angular 2
    Reginald Dawson
  • Code a Real-Time App With NativeScript: Push Notifications
    React Native
    Get Started With React Native
    Markus Mühlberger
  • Code a Real-Time App With NativeScript: Push Notifications
    React Native
    Build a Social App With React Native
    Markus Mühlberger