Swift and Firebase

Sending Push Notifications for Chat Messages

Badr Bujbara
Firebase Developers
9 min readJan 5, 2023

--

In this article, we are going to learn how to notify a recipient of a Firebase chat message.

We will use Firebase Cloud Messaging FCM and Cloud Functions for Firebase to achieve this goal.

We need to work on three areas:

  • In our app project, we need to add the Push Notification capability and configure the Firebase Messaging in the AppDelegate file.
  • In our Apple developer account, we need to create an Apple Push Notifications service (APNs) key and upload it to our Firebase project.
  • We finally need to setup a Cloud Function. This is to tell FCM to send a notification whenever a change happens in our Firebase database. In this case, whenever the recipient gets a new message.

Step 1: Configuring our app to receive push notifications

We need to add the Firebase Messaging library to our app. We can add it as a Swift package or use CocoaPods.

Add Firebase Messaging library

To add Firebase Messaging library as a Swift package, follow the steps:

Select the top project name > PROJECT > Package Dependency > then press (+):

Now just search for ‘firebase’ and add the firebase-ios-sdk package:

If you want to use CocoPods, just add the following to the Podfile then install:

pod ‘FirebaseMessaging’

After installing the Firebase SDK Swift package finishes, head to:

TARGETS > your project > Frameworks, Libraries, and Embedded Content

and add the FirebaseMessaging package

Configure the app to receive an FCM token and ask the user to allow push notification

The FCM token is needed to be used by Firebase to identify individual devices. This is how it’s able to send a notification to a particular device.

We just need to link the FCM tokens to their users and store that in the database. We can then target each user with a push notification based on his FCM token.

So let’s do the app work now.

1- In the AppDelegate file, import FirebaseMessaging as well as UserNotifications :

import UIKit
import Firebase
import FirebaseMessaging
import UserNotifications

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
...

2- Now, add an extension to the AppDelegate for MessagingDelegate to get the FCM token using didReceiveRegistrationToken callback method:

extension AppDelegate: MessagingDelegate {
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print(">> FCM TOKEN:", fcmToken)
let dataDict: [String: String] = ["fcmToken": fcmToken]
NotificationCenter.default.post(name: Notification.Name(Constants.FCM_TOKEN), object: fcmToken, userInfo: dataDict)
// Save it to the user defaults
UserDefaults.standard.set(fcmToken, forKey: "fcmToken")
}

Here we’re saving the token to the user defaults.

Add another extension for the UNUserNotificationCenterDelegate callback method:

extension AppDelegate: UNUserNotificationCenterDelegate { 
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Messaging.messaging().apnsToken = deviceToken
}
}

3- In the didFinishLaunchingWithOptions method, set the Firebase messaging delegate to self, set the notification center delegate to self, and request the user to enable push notifications:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
Messaging.messaging().delegate = self
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { isSuccessful, error in
guard isSuccessful else{
return
}
print(">> SUCCESSFUL APNs REGISTRY")
}
application.registerForRemoteNotifications()
return true
}

4- In your code, whenever a user signs in, save their uid and their FCM token in the database.

    func saveUserFcmToken(_ user: User) {
// Get the FCM token form user defaults
guard let fcmToken = UserDefaults.standard.value(forKey: Constants.FCM_TOKEN) else{
return
}
let fcmTokensRef = References.Instance.fcmTokensRef
var dic = [String: Any]()
dic["token"] = fcmToken
dic["timestamp"] = Utility.getNowTimestamp()
fcmTokensRef.child(user.uid).setValue(dic)
print(">> UPDATED FCM TOKEN: ", "USER: ", user.uid, "FCM TOKEN: ", fcmToken)
}

In the database I have a node called fcmTokens, where I save a dictionary of an FCM token and a timestamp for each user based on their uid.

The timestamp might be helpful in the future as Firebase documentation recommends updating the FCM token after two months since it can get idle.

Now, our app is all set to receive push notifications as well as an FCM token and save the token in the database for the logged in user.

Time for a little break! :)

Step 2: Create APNs key and add it to the Firebase project

Any push notification shows up on an Apple device needs to come from Apple Push Notifications service APNs. In our case, we need to tell Firebase about how to communicate with APNs. We can do that by generating an APNs key from our Apple developer account and save it to our Firebase project.

1- Create an APNs key by signing into your Apple developer account ->

Then, select Apple Push Notifications service (APNs) and download the key so we can use it shortly in our Firebase project.

2- Upload the key to the Firebase project by going to your Firebase Project settings then selecting Cloud Messaging tab:

In there, just upload the key you just downloaded from Apple account.

This concludes our second part, where we setup our Fireable project with an APNs key. In the next step, we’re going to create the cloud function that will send the push notification.

Step 3: Create and setup the Firebase cloud function

The Cloud Function will monitor any change that happens to a node in the database, in this case a message received, and it will tell FCM to send the notification to the receiver.

It will do that by getting the user id of the receiver and fetch their FCM token from the database, then it tells FCM to send a notification to the device with that FCM token.

Here are the steps to follow in order to build a Cloud Function project:

1- Install Node.js on your machine using Homebrew. Here is the command to use in the Terminal in order to install Node.js:

$ brew install node

2- We need to install Firebase Command Line Interface or CLI. We do that using the Node Package Manager command npm. That’s why we installed Node.js in the first step. In the terminal, type the following:

$ npm install -g firebase-tools

You might need to use sudo to install it, so an alternative way would be:

$ sudo npm install -g firebase-tools

Nice! now we can firebase to execute a command in terminal.

3- The recommended language to write cloud functions is TypeScript. We need an editor to write TypeScript code. I found Visual Studio Code a very good editor. You can download it from this link:

4- In order to work with our Firebase project, we need to log into our Firebase account in terminal using the firebase keyword, but before we do that, we need to create a folder for our Firebase functions project. In terminal, cd to your created project folder and type:

$ firebase login

This will take you to a login screen on the browser. After you log in come back to your terminal and initialize the project like this:

$ firebase init

The Firebase CLI will now ask you what Firebase features you want. Use the arrow key to select Functions, then press the spacebar to select it:

Then select the Firebase project you want in your account or create a new one:

Then, select TypeScript as a language, and enter Yes to use TSLint because it’s a helpful tool to diagnose bugs and what’s wrong in your code.

It will install some files and ask if you want to install some dependencies, enter Yes.

Then, it will configure some additional files and should complete initialization successfully.

While at the same directory open Visual Studio Code by typing the following:

$ code .

5- Now, you have the Cloud Functions project opened, we can start typing some code and create our functions.

In the project go to src and click index.ts

This is where our Cloud Functions will be created and written.

Now, it’s time to write the function that will observe if a message is sent, it will notify the receiver.

In this file, index.ts, let’s first import the following:

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";

admin.initializeApp();

functions is a modifier to access the Firebase functions module

admin is a modifier to access our Firebase database. Notice that we initialized it before doing anything with it.

Let’s now define the function that will listen to messages created then send a notification to the receiver:

// 1
export const chatMessageSent = functions.firestore
.document("rooms/{roomId}/thread/{messageId}")
.onCreate((snapshot, context) => {
// 2
const message = snapshot.data();
const recipientId = message.recipientId;
console.log("RECIPIENT ID: " + recipientId);
// 3
admin.database().ref("fcmTokens").child(recipientId).child("token")
.once("value").then((tokenSnapshot) => {
const token = String(tokenSnapshot.val());
const payload = {
notification: {
title: String(message.senderName),
body: String(message.content),
sound: "default",
},
};
admin.messaging().sendToDevice(token, payload);
}
);
return Promise.resolve;
});

First, we specify the path of the database document that we want to listen to and do something whenever a document is created.

In this case each chat has a unique room id, and all messages in the room are saved in a thread with a unique id for each message. Notice we surround unique fields with curly brackets, such as {roomId} and {messageId}.

Second, we create a constant called message to get the data dictionary from the snapshot. The message object will include all fields we saved in the database. For example, Here is a Message swift struct that contains all fields we can retrieve:

 struct Message { 
var roomId: String
var roomTitle: String
var senderId: String
var senderName: String
var recipientId: String
var content: String
var downloadURL: URL?
var sentDate: Date
}

Here, we are getting the recipientId.

We also log this id to check it in case of errors on the Firebase console logging system. We access that from our Firebase project.

Third, we use admin to access the database and retrieve the FCM token of the user that received the message. We also create a notification payload that includes title, body, and sound fields. Then, we use admin again to communicate with FCM and tell it to send a notification to the device with the specified token.

After that, while we still at the terminal at our functions project, we deploy our function using this command:

$ firebase deploy

It could take some time to finish deploying. This will create our function and will add it to the Functions section in our Firebase project.

This is it. We can test our work and try to send a message and see if the receiver gets the push notification :)

In case of problems and you want to check the logs, we can view them on Firebase logging console.

Go to Functions section in the Firebase project and hover over the function we deployed, and it’ll show a three dot menu:

Click it and select View logs option.

This concludes the work needed to implement automated Firebase Cloud Messaging FCM to respond to an event in the Firebase database, in this case a chat message sent.

I hope you learned with me how this works ;)

--

--

Badr Bujbara
Firebase Developers

Software engineer specialized in iOS and Android development.