Firebase Push Notifications

We used to send push notifications directly to APNS or GCM from our code. We were maintaining device_ids, OS types and GCM/APNS certificates. This approach was complicated and hard to maintain.

We knew that an easier solution must exist and then we discovered Firebase.

Firebase is a service which does all the hard work for us. There is no need to store certificates on the server because Firebase handles them for us. Furthermore, we don't need to maintain OS types, because Firebase knows which notification goes to APNS and which to GCM. Basically, Firebase handles the routing and delivery of push notifications to targeted devices. Firebase also has a dashboard with statistics which mobile developers can use for testing purposes.

There are many services (like Amazon SNS) which provide push notifications, but we use Firebase because of its simplicity and reliability.

General

In this chapter, we will explain a few approaches for implementing push notifications and give the pros and cons of each.

Before starting development, read chapters about Firebase Cloud Messaging, and app server development.

Development Approaches

There are a few approaches for sending push notifications using Firebase: * sending directly to device_ids * storing one device id per user * storing multiple device ids per user * sending to topics * with persisted topics * with non-persisted topics * sending to device groups

Approach based on sending notifications directly to device_ids

Having one device id per user

Having multiple device ids per user

API sessions controller example

    class SessionsController < ApiController

      def create
        # login logic goes here
        add_user_device
        # render user
      end

      def destroy
        # logout logic goes here
        device.destroy if device.present?
        # render user
      end

      private

      def add_user_device
        if device.exists?
          device.update(user: current_user)
        else
          current_user.devices.create(device_id: params[:device_id])
        end
      end

      def device
        @device ||= Device.find_by(device_id: params[:device_id])
      end
    end

Sender service example (works for both approaches using device_ids)

    module FirebaseCloudMessaging
      class UserNotificationSender

        attr_reader :message, :user_device_ids

        # Firebase works with up to 1000 device_ids per call
        MAX_USER_IDS_PER_CALL = 1000

        def initialize(user_device_ids, message)
          @user_device_ids = user_device_ids
          @message = message
        end

        def call
          user_device_ids.each_slice(MAX_USER_IDS_PER_CALL) do |device_ids|
            fcm_client.send(device_ids, options)
          end
        end

        private

        def options
          {
            priority: 'high',
            data: {
              message: message
            },
            notification: {
              body: message,
              sound: 'default'
            }
          }
        end

        def fcm_client
          @fcm_client ||= FCM.new(Rails.application.secrets.fcm['server_api_key'])
        end
      end
    end

Approach based on sending notifications to topics

Sender service example

    module FirebaseCloudMessaging
      class UserNotificationSender
        attr_reader :message, :topic

        def initialize(topic, message)
          @topic = topic
          @message = message
        end

        def call
          fcm_client.send_to_topic(topic, options)
        end

        private

        def options
          {
            priority: 'high',
            data: {
              message: message
            },
            notification: {
              body: message,
              sound: 'default'
            }
          }
        end

        def fcm_client
          @fcm_client ||= FCM.new(Rails.application.secrets.fcm['server_api_key'])
        end
      end
    end

Approach based on sending notifications to device groups

Choosing the right development approach

Since we are rather new at using Firebase, please talk to someone from the team before choosing a development approach.

Setup

  1. DevOps has to create a new Firebase project.

  2. The server key from the Firebase project settings should be copied to Vault. It authorizes your app server on Google services.

  3. Add fcm gem to your project. The Fcm gem serves as an SDK for communicating with the Firebase.