Notification Broadcast using BitVector
August 14, 2021 by Lavish Swarnkar
Namaskaram,
In this blog, I will be sharing an amazing story where I used BitVector & Bitwise operators to save myself from writing complex code & also using up a lot of Server Bandwidth.
The problem
During my journey as an Android Developer, I came across a use case where I needed to send personalized notifications to my users.
This app was JU eezy, which I developed for our University. I had to send notifications to users based on their profile. In the user profile, there are fields such as gender, year, hostel, section and course.
Now suppose I want to send notifications to a User segment based only on the following criteria:-
-
Gender - Male
-
Year - 1st & 2nd
-
Hostel - Hostel 3 & 4 (out of 5 hostels)
-
Section - A, B, C & D (out of range A…E)
-
Course - B. Tech CSE & ME (out of several courses)
Easy solution
I use Firebase FCM for notifications. It provides 2 ways to send notifications :
-
By token - each user is given a unique token which can be used to send notification to a specific user only.
-
By Topic - we can create groups of people by subscribing them to a particular topic. The notification will be delivered to all subscribers of that topic.
Topic seemed to be the right choice here. Firebase provides the feature of subscribing a user to a maximum of 2000 topics. So, I could create a topic for each user segment like “CSE”, “MCA”, “Male”, “Female”, etc. and then send the notification to topics using a condition.
For the previous example, condition will be “MALE && (1stYr || 2ndYr) && (H3 || H4) && (A || B || C || D) && (CSE || ME)”. And I could easily send the message to the above topics condition. Simple!
My mistake
But at the time of implementing this feature, I hadn’t read the documentation properly and had mistaken the limit of maximum 20 topics for the 2000 topics limit. So, in my case 20 topics won’t be enough as there are more than 20 courses only. Then, I had to think differently to solve this problem. The above solution using topics would work fine. But if there was a limit of 20 topics instead of 2000, other solutions had to be invented.
Actual implementation
Following is the approach I actually implemented.
FCM is free and you can send unlimited messages. I leveraged this to broadcast the message to all users. And then the message will be shown as notification to only those for whom it’s intended.
The user profile is already saved in the User app. So, when the message is received, we can check whether it’s for the user or not. The only thing is - the recipient’s condition needs to be sent with the message. For our previous example, the condition “MALE && (1stYr || 2ndYr) && (H3 || H4) && (A || B || C || D) && (CSE || ME)” will be sent along with the message for condition checking.
The challenge here was how to send the condition and then how to check the condition on the user’s device.
Taking inspiration from CN
Then I remembered what I had learnt in Computer Networks class. When computer A needs to send a packet to computer B, it has to be identified whether computer B belongs to A’s network. For that purpose, we have subnet masks. When B’s IP address is Bitwise ANDed with subnet mask of B’s network, then if the result (B’s network ID)
-
is the A’s network ID then B also belongs to the same network.
-
is not A’s network ID then B belongs to some other network and the packet needs to be sent to the router.
Here, the concept I found useful in my case was Bitwise AND. Subnet mask somehow saved the information of which bits denote network IP. And these bits are extracted from the computer’s IP address using Bitwise AND. The result is then compared to the network ID.
The Selection Hash
For my case, I also created something like a Subnet mask which would tell for whom the message is intended. And for this, I created the following format of 32 bits :
So, the selection hash for our example “MALE && (1stYr || 2ndYr) && (H3 || H4) && (A || B || C || D) && (CSE || ME)” is
10 11000 00110 11110 101000000000000.
(Assuming 18th bit for CSE and 20th for ME)
Now this can be interpreted as a binary number and corresponds to 2960084992 in the Decimal number system.
Cool, so we have encrypted our condition in just a single number.
Profile Hash
The way we have selection hash, we can also create a profile hash for each user. For example, consider a user who is :
-
Male
-
from 3rd year
-
Hostel 5
-
Section C
-
Course CSE
So, the Profile Hash would be : 10 00100 00001 00100 100000000000000 or 2282897408 in Decimal.
Checking membership
Now whenever a message is sent, it will have a Selection Hash as Recipient information and will be broadcasted to all users. On arriving on the User’s device, it will be Bitwise ANDed with their respective Profile Hash. If the result = selectionHash & profileHash =
-
= 0, then none of the conditions is met and the message is not for the user.
-
greaterThan 0, then at least one of the conditions is met but not necessarily all.
-
= ProfileHash, then all conditions are met
Example :
The easiest way
BitVector was my way of filtering notifications. But Firebase already has a solution for this problem. Firebase Analytics allows you to set user properties like gender, city or any other custom property like Hostel, Course we needed here.
- To create a new user property, goto Firebase Console ➜ under Analytics, Custom definitions.
-
In your app, you can set the created user property
FirebaseAnalytics.getInstance(this).setUserProperty("gender", "male")
-
While sending notifications from the console, you can filter out based on the created user properties :
Conclusion
-
Going the hard way, I got to implement a Computer Networks concept in real life. I was able to look through the problem using CN’s lens.
-
Using bits, you can save more information in less space
-
Using Bitwise operators you can avoid writing a clutter of if-else statements