How we easily added real-time updates to our existing app using Firebase

April 19, 2018

Users now days have high expectations of their experience when using apps, wanting notifications and updates as soon as relevant new content is available. If you’re building an app from scratch then solutions such as the Firebase real-time or Firestore databases provide real-time data sync straight out of the box. Or if you don’t mind managing your own database then there are other options such as RethinkDB.

However bolting on on real-time updates to an existing app can be a difficult or large piece of work. The original version of our app dates back to early 2015 when the expectation for real-time updates wasn’t as high. Customers and users were quite happy with our cross-platform Ionic app which did the job well enough.

With our Cloneder app we were able to add in real-time updates to our chat functionality with very minimal changes, and using our existing framework and database. Here’s how we did it…

Our app uses the Parse framework which supports using either Mongo or PostgreSql as the underlying database. Parse does support real-time updates using the Live-query, however that restricts the database choice to Mongo and adds additional deployment/ops complexity.

Parse live query

Adding our own web socket solution such as socket.io could have been an option, but seeing as we already run on the Google App Engine, utilising Firebase made sense for the simplicity of a serverless solution.

The approach we took was a hybrid mix of the Firebase real-time database and our existing database. By using Firebase purely for the real-time notifications, and not the data, the same design can be used in conjunction with any database (Relational, NoSQL etc) of your choice.

A relatively new option with Firebase is the new Firestore database, which provides a real-time database on the Spanner technology. However he original Firebase real-time database looked to be the most suitable choice due to its lower latency and most likely its pricing. While we didn’t run tests the real-time database charges for the data stored and transferred, while Firestore charges per read and write. As we are writing and transferring small amounts of data, its likely the real-time database would end up cheaper.

The Firebase real-time database rules were updated to allow public reads and disallow writes from clients. As we don’t store any user data, only last modified times, we can leave the data public. While clients can’t write to the database, we can write to it from the server using the Firebase admin SDK.

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

On the server side the changes were isolated to a single callback. Parse allows registering a postSave hook for a database type, so a hook was added for a Chat type. It checks if an update had happened to the chat that a client would be interested in, i.e. the state changed or a new message sent, or if a user has been added/removed to a group chat. If so, then for each user in the chat the path chatsLastUpdated/USER_ID is updated with two values, the chat id and the timestamp.

Database schema

In the client app (which could be either in a mobile app or web app or PWA) it subscribes to path in the real-time database for the logged in user (chatsLastUpdated/USER_ID). When an update comes through the app calls the App Engine server to retrieve the latest updates for the users chats, and if the update is for the currently viewed chat, then the app also requests the latest messages for that chat.

Before the Firebase integration the Chat Service in the app would periodically poll for updates. So the load on the server would scale with the number of active users, and not just the amount of chat activity. By using Firebase to notify when there is an update the benefits are two-fold:

  1. The user get the updates much quicker, improving their experience, instead of waiting up to as long as the polling period
  2. The server load is reduced, saving dollars, by eliminating all the queries which return an empty result signifying no changes.

Should the Firebase rx.js Observable subscription emit an error, or fail to subscribe, then the app falls back to a polling periodically to ensure the chats still update. Likewise when an update comes through from Firebase if the polling interval has previously been started then it is canceled.

At the end of the day the changes required to enable the real-time updates was surprising small, and greatly improves the experience of using the app.