Efficient use of Installation for Push Notification

I am learning how to efficiently handle push notifications targeted with Installation (example chat messages) and I have noticed nice solution from uzaysan where he propose to add User to the Installation.

I also noticed the Installation is not being deleted automatically when user logs out.

Therefore I would have few questions:

  1. In this case the field containing User in Installation should be indexed, right? Or does someone adds Installation.objectId value to an array in User, so there is direct query based on objectId?

  2. As it is not automatically deleted and saved, when is the proper time to delete / save it? On each app launch as mentioned here seem to be inefficient load.

  3. I am thinking rather to chain it with login/logout or Session deleteTrigger methods. But there would need to be another index on Session.installationId. Can someone give an example for higher scale chat app? Adding Installation.objectId to an array in User seems to be more efficient for the database.

  4. Is it a good practice to keep LiveQuery and Push Notification to exist simultaneously while the app is running? Implementing ā€œonlineā€ presence seems to be extra complexity not worth of implementing just to prevent simultaneous run of both LiveQuery and Push Notification.

1 Like

Depends on how many real person shares a single account. But in real life only a few people would have access to same account so both options should work. But I prefer to store it in Installation class since parse SDKs create one for every device. But If your app supports multiple account without re-login (like instagram or twitter) Installation class will not work. Because new logged in user will override the old users Installation class. Installation class is created for different devices not different accounts(Parse Users). Also Installation ıd only change when user uninstall/install the app.

I was saving it on the app launch.

I think its not worth the effort. Send push notification. And if user is on the chat screen dont show it. If user is on somewhere else or app is closed, show notification.

I implemented my own custom notification solution and completely bypassed parse push system.

First I created a custom class named DeviceToken. It had two fields. user (pointer to _User class) and token (string).

Then I created a cloud code to save tokens.

Parse.Cloud.define('saveToken', async ({user, token}) => {
  if (!user || !token || token.length === 0) return;
  const checkToken = new Parse.Query('DeviceToken');
  checkToken.equalTo('user', user);
  checkToken.equalTo('token', token);
  const tokenObject = await checkToken.first(); 
  if (tokenObject) {
    //We already have this token we dont need to do anything
    return;
  }
  //This is a new token we need to save it
  const DeviceToken = Parse.Object.extend('DeviceToken');
  const deviceToken = new DeviceToken();
  deviceToken.set('user', user);
  deviceToken.set('token', token);
  return await deviceToken.save();
});

I run this cloud code on everytime app starts.

Map<String, String> params = new HashMap<>();
params.put('token', firebaseToken);
ParseCloud.callFunctionInBackground('saveToken', params);

And I send notifications with firebase-admin package.

const admin = require("firebase-admin");
//You have to download this json file from your firebase console.
const serviceAccount = require("./serviceAccountKey.json");

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

And use this when you want to send push notifications. For example you want to send notification when a new message is saved.

Parse.Cloud.afterSave('Message', async ({object, original}) => {
  if (!original) {
    //This is a new message so we send notification;
    const reciever = object.get('reciever');
    //Get Device tokens
    const getTokens = new Parse.Query('DeviceToken');
    getTokens.equalTo('user', reciever);
    const tokens = await getTokens.find();
    
    const tokenArray = [];
    for (const token of tokens) {
      tokenArray.push(token.get('token'));
    }

    const message = {
      data: {
        type: 'NEW_MESSAGE', 
        message: 'Example message!'
      },
      tokens: tokenArray,
    };

    await admin.messaging().sendMulticast(message);
  }
});

With this way we dont override if same device has multiple accounts. Every account still continue to recieve notifications. And multiple devşces that has same parse account will get notification for that account.

Thank you for a great example!

In my case there will be only one user per device, but I am considering to implement support for multiple devices per user. So Installation will have only one User and one User can have more than 1 Installation. I believe that indexing that ā€œuserā€ field in Installation is the correct scalable way and my guess is that you also indexed ā€œuserā€ field in DeviceToken as you query if on that field getTokens.equalTo('user', reciever);

So you keep Installation objects (or in your case DeviceToken objects) around no matter if user is logged in or not and you just update the ā€œuserā€ field when login/logout happens, did I understood it right? I am thinking to chain it also to expired sessions and sessions deletion to keep as few Installation objects as needed.

As per APN documentation it is not guaranteed when push notifications are delivered, so for life chat screen I should still keep both, shouldnā€™t I? Or are in reality push notifications as reliable or even more reliable as LiveQuery web socket?

I like your example and solution for n-users on one device. May I ask why you went for Firebase push notifications? I am getting the impression that Parse push notifications are not a good tool to rely on on a big scale (according the documentation there is no dispatch queue and I noticed somewhere in documentation from back4app that they implemented some other solutionā€¦ I might be wrong)

No. my app was only usable if user is logged in. So I only saved Installation class when there is a logged in user.

Oh I see what you mean now. LiveQuery and push notification are not a replacement for each other. They have different behaviours. If you need realtime communication you should choose LiveQuery. But if you want to send the data to user no matter what, then push notification is better. In fact I made a chat app that send messages with push notification and doesnt use livequery. Its not a commercial app but ıt can give you a hint. But its Android and Java.

Edit: I want to explain my approach in this project. If you use both live query and push notifications, data consistency might be effected Users can see duplicated messages. In my app I used SQL Lite database. My UI was getting data from only this sql database. And When I get data from notification service, I was saving it to SQL database. This also provided offline support for chat.

You can do the same. Save data you get from push and live query to SQL database. And feed your list from SQl database. So your list will have only one source of data.

You can use both push notifications and livequery. Use livequery as primary and if you have connection issue, then support it with push notification.

I couldnā€™t make parse push work. Docs for push notification is not up to date. Its not specific to just push notification. Docs are ā€˜sweet spotā€™ of parse. And firebase docs were better. And I had better control over it. And parse uses firebase under the hood.

Iā€™m not sure what you mean by that. But push notifications are stored 28 days at google servers. And they sent when user gets back online. If user doesnā€™t get online in 28 days they get deleted. You can see the source: Life of a message from FCM to the device You can set a shorter time for that. But 28 days is max.And I dont think parse modifies this value. For IOS this should be similar.
This is valid for Android. But not sure for ios. I dont have ios development experience.

I dont think so. Back4App wants you to enter GCM Sender ID. Which you can get it from Firebase (Which is same with Parse Server). Maybe @davimacedo can enlighten us.

I was talking about Android. I dont have information for IOS. Sorry.

1 Like

I see, thank you for additional explanation! I believe that my idea is aligned with your approach then. I am using Realm for offline-first app and there have each object ā€œlocalIdā€ different from ā€œobjectIdā€, so the Parse objectId stays around and is still unique - therefore there would be no duplicates.

As i am still playing learning how to use the push notifications at all, I will try the Parse version first and will keep your firebase example near! Thank you! I agree that I have been confused by the documentation a little - especially back4app is quite behind in this regard. If it is really using firebase under the hood, then it should have similar performance I naively believe.