Device to Device Push Notification using Cloud Functions for Firebase

Chizoba Ogbonna
Chizoba’s Blog
Published in
6 min readMar 27, 2017

Recently, Cloud Functions for Firebase was announced. According to Firebase’s documentation, “Cloud Functions is a hosted, private, and scalable Node.js environment where you can run JavaScript code.”

So what does it do?

Simply put, it lets you write small JavaScript functions, that are deployed to Google Cloud, and are executed in response to firebase events like, a change in your realtime database, authentication, analytics, storage etc.

some use cases:

  1. You want to send a verification email when a user registers on your app.
  2. You want to notify users when there’s a change of value in the database.
  3. You want to resize or manipulate an image uploaded to firebase storage.

In this tutorial, I’d be implementing device to device push notification in an android application. A user writes to the database, and all other users, who subscribed to that node(location) in the database, get notified.

Are you EXCITED, let Get Started!

Setting up your Cloud Function

Install Firebase CLI (Command Line Interface)

npm install -g firebase-tools

NB: The Firebase CLI requires Node.js and npm, which you can install by following the instructions on https://nodejs.org/. Installing Node.js also installs npm.

Then, initialize your project by:

  1. Running firebase login to log in via the browser and authenticate the Firebase tool.
  2. Going to your Firebase project directory and run firebase init functions

After these, your project structure should look like this

project_folder
+- .firebaserc # Hidden file that helps you quickly switch between
| # projects with `firebase use`
|
+- firebase.json # Describes properties for your project
|
+- functions/ # Directory containing all your functions code
|
+- package.json # npm package file describing your Cloud
# Functions code
|
+- index.js # main source file for your Cloud Functions code
|
+- node_modules/ # directory where your dependencies (declared
# in package.json) are installed

Create and deploy your Cloud Function

I will create a function that will listen for changes in a particular node (messages) in the database, and notify users that subscribed to the node

In your Firebase project folder, go to functions and open your index.js file. Import required modules.

// imports firebase-functions module
const functions = require('firebase-functions');
// imports firebase-admin module
const admin = require('firebase-admin');

Initialize your firebase-admin app instance. It helps to trigger firebase events

admin.initializeApp(functions.config().firebase);

Create the function that sends the push notification

/* Listens for new messages added to /messages/:pushId and sends a notification to subscribed users */
exports.pushNotification = functions.database.ref('/messages/{pushId}').onWrite( event => {
console.log('Push notification event triggered');/* Grab the current value of what was written to the Realtime Database */
var valueObject = event.data.val();
/* Create a notification and data payload. They contain the notification information, and message to be sent respectively */
const payload = {
notification: {
title: 'App Name',
body: "New message",
sound: "default"
},
data: {
title: valueObject.title,
message: valueObject.message
}
};
/* Create an options object that contains the time to live for the notification and the priority. */
const options = {
priority: "high",
timeToLive: 60 * 60 * 24 //24 hours
};
return admin.messaging().sendToTopic("notifications", payload, options);
});

deploy your function by running this command

firebase deploy --only functions

P.S: To debug your cloud functions, go to the cloud function section of your Firebase project’s console and click on logs.

Time to create the Android app :)

Add dependencies for firebase cloud messaging and databse in your app’s build.gradle file

// for firebase messaging
compile 'com.google.firebase:firebase-messaging:9.6.1'
// for firebase database
compile 'com.google.firebase:firebase-database:9.6.1'

Create a Firebase project, and follow the processes to add it to your android app. Also update your database read and write rules in the Firebase console to “true”. This is allow writing to database without authentication. Not recommended for a production application.

{
“rules”: {
“.read”: “true”,
“.write”: “true”
}
}

For your MainActivity.java class

import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.messaging.FirebaseMessaging;

public class MainActivity extends AppCompatActivity {

FirebaseDatabase database;
DatabaseReference myRef;

String dataTitle, dataMessage;
EditText title, message;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// Handle possible data accompanying notification message.
if (getIntent().getExtras() != null) {
for (String key : getIntent().getExtras().keySet()) {
if (key.equals("title")) {
dataTitle=(String)getIntent().getExtras().get(key);
}
if (key.equals("message")) {
dataMessage = (String)getIntent().getExtras().get(key);;
}
}
showAlertDialog();
}

database = FirebaseDatabase.getInstance();
myRef = database.getReference("messages");

title = (EditText) findViewById(R.id.title);
message = (EditText) findViewById(R.id.message);
}

private void showAlertDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Message");
builder.setMessage("title: " + dataTitle + "\n" + "message: " + dataMessage);
builder.setPositiveButton("OK", null);
builder.show();
}

public void subscribeToTopic(View view) {
FirebaseMessaging.getInstance().subscribeToTopic("notifications");
Toast.makeText(this, "Subscribed to Topic: Notifications", Toast.LENGTH_SHORT).show();
}

public void sendMessage(View view) {
myRef.push().setValue(new Message(title.getText().toString(), message.getText().toString()));
Toast.makeText(this, "Message Sent", Toast.LENGTH_SHORT).show();
}

}

For your activity_main.xml file

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#dadada"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.github.chizoba.firebasecloudfunctions.MainActivity">


<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:cardCornerRadius="8dp">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Data Payload"
android:textSize="14sp"
android:textStyle="bold" />

<android.support.design.widget.TextInputLayout
android:id="@+id/data_title_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<EditText
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Title" />

</android.support.design.widget.TextInputLayout>

<android.support.design.widget.TextInputLayout
android:id="@+id/data_message_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<EditText
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Message" />

</android.support.design.widget.TextInputLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="horizontal"
android:weightSum="2">

<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:onClick="subscribeToTopic"
android:text="SUBSCRIBE"
android:textColor="#FFFFFF" />

<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_weight="1"
android:background="@color/colorPrimary"
android:onClick="sendMessage"
android:text="SEND"
android:textColor="#FFFFFF" />

</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>

</LinearLayout>

Also, create a class (service) that extends FirebaseMessagingService which listens for changes in your database. Override onMessageReceived which receives an object containing the message sent.

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.support.v7.app.NotificationCompat;
import android.util.Log;

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class FCMService extends FirebaseMessagingService {
private static final String TAG = "MyFirebaseMsgService";

@Override
public void onMessageReceived(RemoteMessage remoteMessage) {

/* There are two types of messages data messages and notification messages. Data messages are handled here in onMessageReceived whether the app is in the foreground or background. Data messages are the type traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app is in the foreground. When the app is in the background an automatically generated notification is displayed. */
String notificationTitle = null, notificationBody = null;
String dataTitle = null, dataMessage = null;

// Check if message contains a data payload.
if (remoteMessage.getData().size() > 0) {
Log.d(TAG, "Message data payload: " + remoteMessage.getData().get("message"));
dataTitle = remoteMessage.getData().get("title");
dataMessage = remoteMessage.getData().get("message");
}

// Check if message contains a notification payload.
if (remoteMessage.getNotification() != null) {
Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
notificationTitle = remoteMessage.getNotification().getTitle();
notificationBody = remoteMessage.getNotification().getBody();
}

// Also if you intend on generating your own notifications as a result of a received FCM
// message, here is where that should be initiated. See sendNotification method below.
sendNotification(notificationTitle, notificationBody, dataTitle, dataMessage);
}

/**
// * Create and show a simple notification containing the received FCM message.
// */
private void sendNotification(String notificationTitle, String notificationBody, String dataTitle, String dataMessage) {
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("title", dataTitle);
intent.putExtra("message", dataMessage);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);

Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(notificationTitle)
.setContentText(notificationBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);

NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}

Remember to add your Firebase messaging service class inside the application tag in your AndroidManifest.xml file

<service android:name=".FCMService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>

Now run your app to see the MAGIC :D

The full code of the sample app can be found at this github repo.

This is just one customization, and you can play with it to fit your needs :D. Do like, recommended, share, and most importantly, ask questions or drop feedback in the comment section below. Thanks ;)

--

--