DEV Community

Supertiger
Supertiger

Posted on

FCM Push notification for chat app (Android)

Firebase Push Notifications (Android)

So, I'm currently working on an Android app for my chat client (https://nertivia.tk). I'm new to the mobile developer world. I have been struggling with setting up push notifications for my chat for days until now, I have figured out a way and would like to share with you guys.

Required

• Mongoose / MongoDB (I used mongoose)
• fcm-node / request (i used fcm-node)
• Express.JS
• axios
• nativescript-plugin-firebase
• Firebase account with FCM Enabled (Free)

Setting Up The Schema

First of all, I will create a new Mongoose schema:

// models/devices.js
const mongoose = require("mongoose");

const { Schema } = mongoose;

const devicesSchema = new Schema({
  user: { type: Schema.Types.ObjectId, ref: "users" },
  token: { type: String, unique: true },
  platform: { type: String }
});

module.exports = mongoose.model("devices", devicesSchema);

I have created a schema that contains:
user to find all the tokens created by a user. (A user could have multiple devices, thus multiple tokens from the same user are being created.
token to save the users token. To make the tokens not be duplicated, I added unique: true to the Object.
platform will be used to determine if the token is created from an android device or an Apple device. For now, I will just be focusing on android.

Registering Users Tokens

Next up, I have created a new Express route that will get the fcm token generated by the client.

// routes/devices/registerDevice.js
const Devices = require("../../models/Devices");

module.exports = async (req, res, next) => {
  const { token } = req.body;
  if (!token.trim()) {
    return res.status(403).json({ message: "Token not provided." });
  }
  try {
    await Devices.create({
      user: req.user._id,
      platform: "android",
      token
    });
    res.json({ message: "Done" });
  } catch (e) {
    return res.status(403).json({ message: "token already saved." });
  }
};

First of all, I check if the token exists. if it doesn't exist, I will reject the request. If it exists, I will add it to the database. For now, I will manually define the platform to android as that's my main focus.
If the token is already saved, I will just return an error.

Send Push Notification

This is the last step I took on the server-side, sending the notification when someone sends a message to the user. I will not be showing the full message logic as it is very long. You can see it on my GitHub page here

// routes/messages/sendMessage.js
async function sendPushNotification(user, msg, recipient) {
  const _id = recipient._id;

  // check if notification token exists
  const requestToken = await Devices.find({ user: _id });

  if (!requestToken || !requestToken.length) return;

  const tokens = requestToken.map(t => t.token);

  const msgContent = msg.message;

  const message = {
    registration_ids: tokens,

    notification: {
      title: user.username,
      body:
        msgContent.length >= 500
          ? msgContent.substring(0, 500) + "..."
          : msgContent,
      image: "https://" + domain + "/api/avatars/" + user.avatar
    },
    data: {
      channel_id: msg.channelID
    }
  };

  fcm.send(message, async function(err, response) {
    if (err) {
      console.log("Something has gone wrong!");
    } else {
      // remove all expired tokens from db.
      const failedTokens = response.results
        .map((r, i) => r.error && tokens[i])
        .filter(r => r);
      await Devices.deleteMany({ token: { $in: failedTokens } });
    }
  });
}

_id is the user's id. The database will look for tokens that are associated with the id. the requestToken should contain all of the user's tokens if they exist. if they don't exist, I will return the function so it no longer executes.

The message variable contains all the information that will be sent to Firebase and then to the user's device. registration_ids requires an array of tokens. I have provided it with tokens that the database found using the users _id.

I have also done some message checks. If the message is variable is longer than 500 characters, I will clamp it down to 500 characters and send it. This is because it will be unsisterly to send a lot of data just for a notification.

the fcm.send the function will send the notification to all of the tokens that were provided in the variable. If some of the tokens get failed, I will remove them from the database. Tokens usually get failed if the user has logged out or uninstalled the app.

Register and send the token

This is the last step, setting up the client. I used nativescript-vue as I am experienced with vue.js. Again, this code is too long. I have not uploaded this code on GitHub yet, I will upload it soon.

//components/LoginPage.vue
import axios from 'axios'
import { messaging, Message } from 'nativescript-plugin-firebase/messaging';
export default {
  methods: {
    ...LoginClickEvent,
    async registerPushNotifications() {
      messaging.registerForPushNotifications({
        onPushTokenReceivedCallback: async (token) => {
            // posts the token to registerDevice route.
            axios.post('https://nertivia.tk/api/devices', {
                token: token,
            })
        },
        showNotificationsWhenInForeground: true
      }).then(() => console.log("Registered for push"));

      return;
    },
 }
}

The code above will be executed after the login was authenticated. The onPushTokenReceivedCallback event will register and give the token. After we get the token, we will send it to the registerDevice route that we created using axios. then showNotificationsWhenInForeground will make sure the notifications will appear when the app is closed.

Now, your notifications should be ready and already appearing. If you find any better, or more efficient ways, please do let me know in the comments. I hope I helped you guys with this blog if you've been struggling as I did.

Top comments (0)