Add serverless Firebase push notifications to React Native chat app tutorial

0:00

That was the hardest thing in coding that I’ve ever done. Setting up Firebase Cloud Messaging with a React Native app in March/April 2017 is hell. I hope the ecosystem improves.

After 7 days, it works! As far as I know, this is the only comprehensive how-to out there. Enjoy 👇

Notifications firing on the phone

Before you ask: yes, notifications fire when your app is in the background. And yes, the app fetches new messages in the background too 🙂

Here’s what you’ll learn in today’s lecture:

  • how to convince Apple to let you use notifications
  • how to run your app on a real device
  • how to make React Native and Firebase native SDK get along
  • how to setup Firebase with your Apple certificates
  • how to trigger notifications with Firebase Cloud Functions

Today’s lecture is part 2 of Build a Chat App with Firebase and Redux. I assume you’re continuing from there and already have the base Chat App defined.

You don’t need the Chat App to follow this lecture. As long as your app is React Native first, created with react-native init, then this guide is for you.

Some of this guide is iOS specific. We’re using the platform agnostic react-native-fcm library, so you can use most of this guide for Android as well. But I haven’t tested that because I don’t have an Android device.

That’s a lot of disclaimers … basically this is the only way I’ve found to implement serverless push notifications. This is also the only comprehensive guide to integrating the native Firebase SDK with React Native 0.42.x or 0.43.x. Google found nothing but random StackOverflow questions and outdated documentation

You can see the final code on Github.

Apple Developer Account

First thing’s first: We have to pay our dues to Apple. To play with push notifications on iOS, you need an Apple Developer account. It costs $99 + tax and a day or two of waiting. You’re also going to need an iDevice because the simulator doesn’t receive push notifications.

Having an Apple Developer account lets you run apps on your phone, publish them to the app store, and things like that. I don’t know everything you can do, but it sounds like anything even advanced or proprietary requires a paid account with a confirmed identity.

They asked for a photo of my photo ID. ‍♂️

The easiest way to create a developer account is through XCode. Same process as connecting an existing account, except you click Add new account instead of picking an existing one.

Open your project’s ios/<name>.xcodeproj file with XCode, navigate to General settings and look for Signing config. This part videos particularly well. Watch the video at around 1min, if you’re confused.

I was very confused looking at this for the first time.

If you don’t have a Developer Account yet, the interface will guide you through creating one. That part happens through your browser and involves

Set up certs and enable Push Notifications on Firebase and Apple

After you’ve got your Apple Developer Account, it’s time to set up your certificates. You need two of them: One for running your app on a device, another for push notifications.

Setting up certificates is annoying, I agree. But it improves app security. When notifications are signed, your app can be sure you sent them. When your app is signed, a device can be sure it’s your app.

Imagine if any random person on the internet could harass your users with notifications or publish app that look just like yours, but steal users’ info? No good, right? Hence, certificates.

The setup videos well and texts poorly. I suggest watching the video from about 2min onwards.

Here’s what you do in a nutshell:

  1. Create a certificate signing request with your Keychain App
  2. Create an App ID in Apple Developer portal. Make sure your Bundle ID in XCode and on the site match
  3. Create a new certificate
  4. Pick certificate type: App Development or Push Notification
  5. For notifications certificate, pick your App ID
  6. Download both certificates
  7. Double-click certificates to install in your Keychain

Create app in Firebase, add certificate

Once you have both certificates installed locally, you have to give the Push Notifications one to Firebase. That way it can sign notifications before sending them.

Video is your friend. 🙂 Here’s what you do in a nutshell:

  1. Find your push notification certificate in Keychain
  2. Export into p12
  3. Go to Firebase Console, click Add App
  4. Make sure Bundle ID is same as in XCode
  5. Skip config stuff for now
  6. Go to Cloud Messaging tab in app settings
  7. Upload Certificate

Enable Push Notifications in XCode

So many steps! Two more 🙂

Go back to XCode, find the Capabilities tab, and enable Push Notifications. You should see two grey checkmarks. I don’t know why they’re not green, I was quite disappointed.

They’re def red when something goes wrong … so unfair.

The library we’re using – react-native-fcm – suggests enabling Keychain Sharing and Background Modes as well. I don’t know what keychain sharing does, and I think background modes lets us accept notifications when our app is in the background.

Run your app on a device

Press Play on top of the XCode window. Wait.

Your device should show an app. If it doesn’t … literally any of the steps above could have gone wrong. I suggest going over again slowly. Google any errors that you see.

Enable Firebase SDK without exploding your app

Now for the fun part: Installing the Firebase SDK without using CocoaPods. No matter what the various README files and documentation tell you, CocoaPods and React Native do not like each other.

I realized this after a careful process of elimination and two or three apps started from scratch. There is no sane way to get it working.

As soon as CocoaPods enters a project that was started with react-native init, the project explodes. Core React libraries become unavailable and if you start fixing imports, you will never finish. Fix one, the next one breaks.

The solution that I’ve found is to install Firebase SDK manually. Download from Firebase docs, then add every .framework file in Firebase/Analytics and Firebase/Messaging to your app.

Now your app has the SDK.

But it lacks two core libraries that Firebase uses. I don’t know why, but they’re libz.dylib and libsqlite3.dylib.

You can add them through the Build Phases tab in your project. They’re in /usr/lib.

¯_(ツ)_/¯

Initialize the SDK

For the initialization step, you’ll need to copy paste some code and the Firebase config from your console.

Go to your Firebase Console, find your app, and look for a button that says GoogleService-Info.plist. Drag it into your XCode project next to Info.plist. If you open it, you’ll see it contains Firebase authentication stuff.

You should use mine, if you want to receive notifications from the global chatroom. That’s the one everyone from this tutorial is sharing 🙂

Then go into AppDelegate.h and add an import and a parameter to the main interface.

// AppDelegate.h

// add this
@import UserNotifications;

// change this with extra param
@interface AppDelegate : UIResponder <UIApplicationDelegate,UNUserNotificationCenterDelegate>

Changes in AppDelegate.m are more extensive. You have to add an import, two lines to the application function, and a couple functions at the end of the file.

// AppDelegate.m

// add on top
#import "RNFIRMessaging.h"

// add before return YES;
  [FIRApp configure];
  [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];
  
// add before @end
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
  [RNFIRMessaging willPresentNotification:notification withCompletionHandler:completionHandler];
 }

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler
{
     [RNFIRMessaging didReceiveNotificationResponse:response withCompletionHandler:completionHandler];
}

//You can skip this method if you don't want to use local notification
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
  [RNFIRMessaging didReceiveLocalNotification:notification];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler{
   [RNFIRMessaging didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}

I would explain what all this code does, but to be honest, I don’t know. ObjectiveC is a strange foreign land to me.

Two more files

In the video I get an error after adding the code. That’s because I forgot to copy Firebase.h and module.modulemap from the Firebase SDK package.

You should add those too. I think they’re what defines the firebase module in the first place.

Listen for push notifications

That was fun … kind of. Now comes the really fun part: notifications flying around!

Back to the familiar embrace of JavaScript land. I feel more comfortable already. All that stuff we just did, that felt harder than setting up Webpack …

Anyway, the best palce to put JavaScript-side notification code is in Redux thunks. It’s code that does something, so I think it fits in actions or thunks.

We don’t want the notifications dialog to scare people off, but we do want them to be notified of new messages. That means we should put notification code somewhere close to the user is authorized now action.

With some code tweaks since last lecture, I built a startChatting action. It finishes authorizing a user, initiates message fetching, and subscribes to notifications.

// src/actions/index.js

import FCM, { FCMEvent,
              NotificationType,
              WillPresentNotificationResult,
              RemoteNotificationResult } from 'react-native-fcm';
import { Platform } from 'react-native';

// ...

const startChatting = function (dispatch) {
    dispatch(userAuthorized());
    dispatch(fetchMessages());

    FCM.requestPermissions();
    FCM.getFCMToken()
       .then(token => {
           console.log(token)
       });
    FCM.subscribeToTopic('secret-chatroom');

    FCM.on(FCMEvent.Notification, async (notif) => {
        console.log(notif);

        if (Platform.OS === 'ios') {
            switch (notif._notificationType) {
                case NotificationType.Remote:
                    notif.finish(RemoteNotificationResult.NewData); //other types available: RemoteNotificationResult.NewData, RemoteNotificationResult.ResultFailed
                    break;
                case NotificationType.NotificationResponse:
                    notif.finish();
                    break;
                case NotificationType.WillPresent:
                    notif.finish(WillPresentNotificationResult.All); //other types available: WillPresentNotificationResult.None
                    break;
              }
            }
    });

    FCM.on(FCMEvent.RefreshToken, token => {
        console.log(token);
    });
}

That’s more like it! Some familiar code!

We import a bunch of stuff from react-native-fcm, and define a startChatting helper function. Thunks call startChatting and pass it the dispatch function so it can dispatch Redux actions.

Does that make it a thunk?

The important bits are:
FCM.requestPermissions asks the OS for permission to show notifications, that’s the dialog where you often click No
FCM.getFCMToken generates and/or receives a unique notification token for this device. It lets us send messages to specific users. Ideally you’d save this to Firebase 🙂
FCM.subscribeToTopic subscribes users to the secret-chatroom channel. This makes it easy to send notifications to a group of devices.
FCM.on(..., this part handles notifications when they come in.

I don’t know exactly why the notification handling code has to be that way, I got it from the README file. My understanding is that iOS needs us to “finish” notifications in different ways depending on their type.

Android is smart enough to handle things. I guess …

Send notifications on DB changes using Firebase Cloud Functions

Our app is listening for notifications. We still have to fire them, though.

Traditionally people used servers for that. You build a server, you observe data on Firebase, you use the API to send notifications. Yawn

On March 10th, 2017, Google released Firebase Cloud Functions. Supposedly a competitor to AWS Lambda, but I haven’t tried that before. The bottom line is this: You can run code without running a server.

This could almost be its own article, so here’s a quick walkthrough. Full code on Github.

1: You need the firebase CLI tool. It’s in the docs

2: Make a directory, ChatAppFirebaseFunctions maybe

3: firebase login && firebase init

4: Write code

For a simple “notify everyone on new message” function, the code looks like this:

// ChatAppFirebaseFunctions/functions/index.js

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const _ = require('lodash');

admin.initializeApp(functions.config().firebase);

exports.sendNewMessageNotification = functions.database.ref('/messages').onWrite(event => {
    const getValuePromise = admin.database()
                                 .ref('messages')
                                 .orderByKey()
                                 .limitToLast(1)
                                 .once('value');

    return getValuePromise.then(snapshot => {
        console.log(_.values(snapshot.val())[0]);
        const { text, author } = _.values(snapshot.val())[0];

        const payload = {
            notification: {
                title: 'New msg',
                body: text,
                icon: author.avatar
            }
        };

        return admin.messaging()
                    .sendToTopic('secret-chatroom', payload);
    });
});

We export a function that registers an onWrite listener on our list of messages. When messages are written, a push notification will fire.

There’s no need to tell anyone what our function is. If it’s exported, Firebase will figure it out. That part confused me at first.

Inside our listener, we get the last message, use it to build the notification payload, and send it to everyone subscribed to secret-chatroom. Everyone finds out when things happen and with some luck the chatroom becomes a community.

5: firebase deploy

Done. Your code is available right away.

Victory, you have serverless push notifications

Rejoice!

That … that was hard right? Am I just dumb?

Sure felt like a lot of clicking, but we’ve unlocked a new skill: notifications. So many possibilities for future apps!

You should subscribe by email to get a new app walkthrough every 2 weeks. I learn a lot, I’m sure you will too.

Notifications firing on the phone