Sunday, September 2, 2018

Notifications - using Firebase Realtime database

Building successful applications for the web or mobile devices come with few key must haves. One of these must haves is a feature we all love to rely on - notifications. This feature varies in how it can be implemented in an application, and comes with a wide array of possibilities. There was a time when the only way to find out if we have a new email was to log in to the email service form the browser and check for any new mail. Then came desktop clients that would show a dialog on the side of the screen when a new mail arrived. Slowly we started receiving notifications for other products through emails. Over time notifications could be received through SMS, a secondary option to notifications through email. Now all notifications can be received as push notifications on our mobile devices and smartwatches.

As of now, notifications can be received through emails, pushed on to our handheld devices, or  are shown to us when we log in to the application. There are many ways to implement notifications in an application but at the moment we will be talking about how to use capabilities of Firebase Realtime database to our advantage.



Firebase?


Let's start from the very top. Firebase is a mobile and web application development platform developed by Firebase, Inc. in 2011, then acquired by Google in 2014. It provides a list of features that can be used to provide more value to an application. We'll be focusing on only one of them; a  cloud hosted,  single JSON based Realtime database that can sync data with any client application connected to it. All changes made into the JSON get synced instantly to any and all clients. This gives the developer a range of possibilities when it comes to applying to the application.

Limits


There are a few points that should be kept in mind. A Spark plan (free) only allows 1 instance of the Realtime database with data limit of maximum 1GB, with bandwidth limit of 10GB per month. For small scale application use case, 1GB storage and 10GB bandwidth is a lot. But for a large scale application that 1GB can vanish pretty quickly or the 10GB monthly bandwidth can be too restrictive.

Approaches


With the above points to keep in mind, several approaches can be taken to implement the richness of Realtime database into the application for notifications.
  1. My user base is going to be very small and limited bandwidth will be used, so I'll use the Realtime database to host all notifications for all users. When writing new notification messages I will remove messages that are older than some specified time frame so that I never run out of 1GB quota.
  2. I have a large user base and transactions to the database may be higher at times, so I will conserve my limits by only using Realtime database for posting notification counts against users and store the actual notifications in another database (probably the main one used by the application for storing user and other data). This way my client app will know in real-time time if there are new notifications for the user.
  3. I can afford a Blaze plan (pay-as-you-go) and do not need to worry about the data or bandwidth limits.

Project Setup


To introduce Firebase to our application we need to access the Firebase console and create a Project. You will need a Google account to access Firebase console. The console lists all project and can be used to create a new one. I'm creating a Project called MussPeriments.



The project that was just created can be seen on the main page along with any other projects that we have made previously. Let's navigate inside the project and get to the Realtime database.



We will click "Create database" button and select "Start in test mode" in the popup that follows. The two modes offered are just presets for database rules that can be changed into any complex form in any phase of development. The test mode only sets all reads and writes as true allowing all kinds of operations to be performed even without authentication. The locked mode (other option) sets all reads and writes as false making the database private and only allowed for use through admin level access. A private database is of no use if it is intended for interaction with the client app. The application won't be able to sync notification updates from Firebase. The rules are also defined as JSON in the Rules tab, the rules for all write access should look like the following:

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

Schema


What we need now is a notification management strategy as schema for this JSON database. We can do as simple as maintain a list of users that each have an unread count value. This would go well with both the approaches discussed above where we would only maintain either unread count or have the notification messages as well. The list of unread counts should look like the following:

{
  "unread": {
    "user1": 0,
    "user2": 0
  }
}

We can then proceed to add the notifications list as well which in itself can be approached in many ways. Firebase recommends data redundancy if it reduces nest of JSON objects so let's list some possible approaches to storing notifications:

//if all users have shared / common messages
{
  "unread": {
    "user1": 0,
    "user2": 0
  },
  "notifications": {
    "message1": "Sample message text 1",
    "message2": "Sample message text 2",
    "message3": "Sample message text 3",
    "message4": "Sample message text 4"
  }
}

//if all users have separate with some common messages
{
  "notifications": {
    "user1": {
      "messages": {
        "message1": "Sample message text 1",

        "message2": "Sample message text 2"

      },
      "unread": 0
    },
    "user2": {
      "messages": {
        "message1": "Sample message text 1",
        "message3": "Sample message text 3",
        "message4": "Sample message text 4"
      },
      "unread": 0
    }
  }
}

//if all users have separate with some common messages but we need less nesting and simpler structure
{
  "unread": {
    "user1": 0,
    "user2": 0
  },
  "notifications": {
    "user1": {
      "message1": "Sample message text 1",
      "message2": "Sample message text 2"
    },
    "user2": {

      "message1": "Sample message text 1",

      "message3": "Sample message text 3",
      "message4": "Sample message text 4"
    }
  }
}

The above three approaches all differ in little ways and it all boils down to what is needed on the client end. Speaking of which, the client app can then listen to the node for changes. The listening part is done automatically by the Firebase wrappers available. For example in the 1st schema the client app can listen to "unread/user{0}" node, and in case the unread count is greater than 0, show an alert to the user. The client part listening to notification messages will automatically show changes when new notification is added. Nodes for other schema will behave more or less the same.

Client Apps


There are many client app approaches supported by Firebase, most notably JavaScript. Initialize client connection as (replace with your project's config object):

var config = {
  apiKey: "apiKey",
  authDomain: "projectId.firebaseapp.com",
  databaseURL: "https://databaseName.firebaseio.com",
  storageBucket: "bucket.appspot.com"
};

firebase.initializeApp(config);

The get database reference with the following line:

var database = firebase.database();

Then it is just a matter of listening in to the write node for changes / notifications like in the following code:

var userId = firebase.auth().currentUser.uid;

var unreadCountRef = firebase.database().ref('/unread/' + userId);
unreadCountRef.on('value', function(snapshot) {
  var unreadCount snapshot.val();
});

If we wish to read data from a node only once then the following code is application:

var userId = firebase.auth().currentUser.uid;

return firebase.database().ref('/unread/' + userId)
  .once('value')
  .then(function(snapshot) {
    var unreadCount = snapshot.val();
});

I won't delve much into client side implementation as that is a whole post worth of content based on platform. If I'm missing something or something needs explanation feel free to contact me or comment below.