# Push Notifications Using Atlas App Services & iOS Realm SDK

In a serverless application, one of the important features that we must implement for the success of our application is push notifications.

Realm allows us to have a complete push notification system directly in our Services App. To do this, we’ll make use of several components that we’ll explain here and develop in this tutorial. But first, let’s describe what our application does.

## Context

The application consists of a public list of books that are stored locally on the device thanks to Atlas Device Sync, so we can add/delete or update the books directly in our Atlas collection and the changes will be reflected in our application.

We can also add as favorites any book from the list, although to do so, it’ll be necessary to register beforehand using an email and password. We will integrate email/password authentication in our application through the Atlas App Services authentication providers.

The books, as they belong to a collection synchronized with Atlas Device Sync, will not only be stored persistently on our device but will also be synchronized with our user. This means that we can retrieve the list of favorites on any device where we register with our credentials. Changes made to our favorites using other devices are automatically synchronized.

### Firebase

The management of push notifications will be done through Firebase Cloud Messaging. In this way, we benefit from a single service to send notifications to iOS, web, and Android.

The configuration is similar to the one we would follow for any other application. The difference is that we will install the firebase-admin SDK in our Atlas App Services application.

### Triggers

The logic of this application for sending push notifications will be done through triggers. To do this, we will define two use cases:

1. A book has been added or deleted: For this, we will make use of the topics in Firebase, so when a user registers to receive this type of notification, they will receive a message every time a book is added/deleted from the general list of books.
2. A book added to my favorites list has been modified: We will make use of the Firebase tokens for each device. We relate the token received to the user so that when a book is modified, only the user/s that have it in their favorites list will receive the notification.

### Functions

The Atlas Triggers will have a function linked that will apply the logic for sending push notifications to the end devices. We will make use of the Firebase Admin SDK that we will install in our App Services App as a dependency.

## Overall application logic

This application will show how we can integrate push notifications with an iOS application developed in Swift. We will discuss how we have created each part with code, diagrams, and example usage.

At the end of this tutorial, you’ll find a link to a Github repository where you’ll find both the code of the iOS application as well as the code of the App Services application.

When we start the application for the first time, we log in using anonymous authentication, since to view the list of books, it’s not necessary to register using email/password. However, an anonymous user will still be created and saved in a collection Users in Atlas.

When we first access our application and enable push notifications, in our code, we register with Firebase. This will generate a registration token, also known as FCMToken, that we will use later to send custom push notifications.

```swift
Messaging.messaging().token { token, error in
   if let error = error {
       print("Error fetching FCM registration token: \(error)")
   } else if let token = token {
       print("FCM registration token: \(token)")
       // Save token in user collection
       user.functions.updateFCMUserToken(AnyBSON(token), AnyBSON("add")], self.onCustomDataUpdated(result:realmError:))
   }
}
```

Once we obtain the FCM token, we will [call a function through the Realm SDK to save this token in the user document corresponding to the logged-in user. Within the document, we have defined a token field that will be composed of an array of FCM tokens.

To do this, we will make use of the Firebase SDK and the `Messaging` method, so that we are notified every time the token changes or a new token is generated. In our Swift code, we will use this function to insert a new FCToken for our user.

```swift
Messaging.messaging().token { token, error in
    if let error = error {
        print("Error fetching FCM registration token: \(error)")
    } else if let token = token {
        print("FCM registration token: \(token)")
        // Save token in user collection
        user.functions.updateFCMUserToken(AnyBSON(token), AnyBSON("add")], self.onCustomDataUpdated(result:realmError:))
    }
}
```

In our App Services app, we must implement the logic of the `updateFCMUserToken` function that will store the token in our user document.

#### Function code in Atlas

```javascript
exports = function(FCMToken, operation) {

  const db = context.services.get("mongodb-atlas").db("product");
  const userData = db.collection("customUserData");
  
  if (operation === "add") {
    console.log("add");
    userData.updateOne({"userId": context.user.id},
    { "$addToSet": {
      "FCMToken": FCMToken
    }  
    }).then((doc) => {
      return {success: `User token updated`};
    }).catch(err => {
      return {error: `User ${context.user.id} not found`};
    });
  } else if (operation === "remove") {
    console.log("remove"); 
  } 
};
```

We have decided to save an array of tokens to be able to send a notification to each device that the same user has used to access the application.

The following is an example of a User document in the collection:

```JSON
{
 "_id": {
   "$oid": "626c213ece7819d62ebbfb99"
 },
 "color": "#1AA7ECFF",
 "fullImage": false,
 "userId": "6268246e3e0b17265d085866",
 "bookNotification": true,
 "FCMToken": [
   "as3SiBN0kBol1ITGdBqGS:APA91bERtZt-O-jEg6jMMCjPCfYdo1wmP9RbeATAXIQKQ3rfOqj1HFmETvdqm2MJHOhx2ZXqGJydtMWjHkaAD20A8OtqYWU3oiSg17vX_gh-19b85lP9S8bvd2TRsV3DqHnJP8k-t2WV",
   "e_Os41X5ikUMk9Kdg3-GGc:APA91bFzFnmAgAhbtbqXOwD6NLnDzfyOzYbG2E-d6mYOQKZ8qVOCxd7cmYo8X3JAFTuXZ0QUXKJ1bzSzDo3E0D00z3B4WFKD7Yqq9YaGGzf_XSUcCexDTM46bm4Ave6SWzbh62L4pCbS"
 ]
}
```

## Send notification to a topic

Firebase allows us to subscribe to a topic so that we can send a notification to all devices that have ever subscribed to it without the need to send the notification to specific device tokens.

In our application, once we have registered using an email and password, we can subscribe to receive notifications every time a new book is added or deleted.

![Setting view in the iOS app

When we activate this option, what happens is that we use the Firebase SDK to register in the topic books.

```swift
static let booksTopic = "books"

@IBAction func setBookPushNotification(_ sender: Any) {
 if booksNotificationBtn.isOn {
     Messaging.messaging().subscribe(toTopic: SettingsViewController.booksTopic)
     print("Subscribed to \(SettingsViewController.booksTopic)")
 } else {
     Messaging.messaging().unsubscribe(fromTopic: SettingsViewController.booksTopic)
     print("Unsubscribed to \(SettingsViewController.booksTopic)")
 }
}
```

### How does it work?

The logic we follow will be as below:

In our Atlas App Services App, we will have a database trigger that will monitor the Books collection for any new inserts or deletes.

Upon the occurrence of either of these two operations, the linked function shall be executed and send a push notification to the “books” topic.

To configure this trigger, we’ll make use of two very important options:

* **Full Document**: This will allow us to receive the document created or modified in our change event. 
* **Document Pre-Image**: For delete operations, we will receive the document that was modified or deleted before your change event.

With these options, we can determine which changes have occurred and send a message using the title of the book to inform about the change. 

The configuration of the trigger in the App Services UI will be as follows:

The function linked to the trigger will determine whether the operation occurred as an `insert` or `delete` and send the push notification to the topic **books** with the title information.

Function logic:

```javascript
 const admin = require('firebase-admin');
 admin.initializeApp({
   credential: admin.credential.cert({
     projectId: context.values.get('projectId'),
     clientEmail: context.values.get('clientEmail'),
     privateKey: context.values.get('fcm_private_key_value').replace(/\\n/g, '\n'),
   }),
 });
 const topic = 'books';
 const message = {topic};
 if (changeEvent.operationType === 'insert') {
   const name = changeEvent.fullDocument.volumeInfo.title;
   const image = changeEvent.fullDocument.volumeInfo.imageLinks.smallThumbnail;
   message.notification = {
     body: `${name} has been added to the list`,
     title: 'New book added'
   };
   if (image !== undefined) {
     message.apns = {
       payload: {
         aps: {
           'mutable-content': 1
         }
       },
       fcm_options: {
         image
       }
     };
   }
 } else if (changeEvent.operationType === 'delete') {
   console.log(JSON.stringify(changeEvent));
   const name = changeEvent.fullDocumentBeforeChange.volumeInfo.title;
   message.notification = {
     body: `${name} has been deleted from the list`,
     title: 'Book deleted'
   };
 }
 admin.messaging().send(message)
   .then((response) => {
     // Response is a message ID string.
     console.log('Successfully sent message:', response);
     return true;
   })
   .catch((error) => {
     console.log('Error sending message:', error);
     return false;
 });
```

When someone adds a new book, everyone who opted-in for push notifications will receive the following:

## Send notification to a specific device

To send a notification to a specific device, the logic will be somewhat different.

For this use case, every time a book is updated, we will search if it belongs to the favourites list of any user. For those users who have such a book, we will send a notification to all registered tokens.

This will ensure that only users who have added the updated book to their favorites will receive a notification alerting them that there has been a change.

### How does it work?

For this part, we will need a database trigger that will monitor for updates operations on the books collection.

The configuration of this trigger is much simpler, as we only need to monitor the `updates` that occur in the book collection. 

The configuration of the trigger in our UI will be as follows:

When such an operation occurs, we’ll check if there is any user who has added that book to their favorites list. If there is, we will create a new document in the ***pushNotifications*** collection.

This auxiliary collection is used to optimize the sending of push notifications and handle exceptions. It will even allow us to set up a monitoring system as well as retries.

Every time we send a notification, we’ll insert a document with the following:

1. The changes that occurred in the original document.
2. The FCM tokens of the recipient devices.
3. The date when the notification was registered.
4. A processed property to know if the notification has been sent.

Here’s an example of a push notification document:

```JSON
{
 "_id": {
   "$oid": "62a0da5d860040b7938eab87"
 },
 "token": 
"e_OpA2X6ikUMk9Kdg3-GGc:APA91bFzFnmAgAhbtbqXOwD6NLnDzfyOzYbG2E-d6mYOQKZ8qVOCxd7cmYo8X3JAFTuXZ0QUXKJ1bzSzDo3E0D00z3B4WFKD7Yqq9YaGGzf_XSUcCexDTM46bm4Ave6SWzbh62L4pCbS",
   "fQvffGBN2kBol1ITGdBqGS:APA91bERtZt-O-jEg6jMMCjPCfYdo1wmP9RbeATAXIQKQ3rfOqj1HFmETvdqm2MJHOhx2ZXqGJydtMWjHkaAD20A8OtqYWU3oiSg17vX_gh-19b85lP9S8bvd2TRsV3DqHnJP8k-t2WV"
 ],
 "date": {
   "$date": {
     "$numberLong": "1654708829678"
   }
 },
 "processed": true,
 "changes": {
   "volumeInfo": {
     "title": "Pacific on Linguistics",
     "publishedDate": "1964",
     "industryIdentifiers": [
       {
         "type": "OTHER",
         "identifier": "UOM:39015069017963"
       }
     ],
     "readingModes": {
       "text": false,
       "image": false
     },
     "categories": [
       "Linguistics"
     ],
     "imageLinks": {
       "smallThumbnail": "http://books.google.com/books/content?id=aCVZAAAAMAAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api",
       "thumbnail": "http://books.google.com/books/content?id=aCVZAAAAMAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api"
     },
     "language": "en"
   }
 }
}
```

To process the notifications, we’ll have a database trigger that will monitor the ***pushNotifications*** collection, and each new document will send a notification to the tokens of the client devices.

#### Function logic

```javascript
exports = async function(changeEvent) {

 const admin = require('firebase-admin');
 const db = context.services.get('mongodb-atlas').db('product');

 const id = changeEvent.documentKey._id;

 const bookCollection = db.collection('book');
 const pushNotification = db.collection('pushNotification');

 admin.initializeApp({
   credential: admin.credential.cert({
     projectId: context.values.get('projectId'),
     clientEmail: context.values.get('clientEmail'),
     privateKey: context.values.get('fcm_private_key_value').replace(/\\n/g, '\n'),
   }),
 });

 const registrationToken = changeEvent.fullDocument.token;
 console.log(JSON.stringify(registrationToken));
 const title = changeEvent.fullDocument.changes.volumeInfo.title;
 const image = changeEvent.fullDocument.changes.volumeInfo.imageLinks.smallThumbnail;

 const message = {
   notification:{
     body: 'One of your favorites changed',
     title: `${title} changed`
   },
   tokens: registrationToken
 };

 if (image !== undefined) {
   message.apns = {
     payload: {
       aps: {
         'mutable-content': 1
       }
     },
     fcm_options: {
      image
     }
   };
 }

 // Send a message to the device corresponding to the provided
 // registration token.
 admin.messaging().sendMulticast(message)
   .then((response) => {
     // Response is a message ID string.
     console.log('Successfully sent message:', response);
     pushNotification.updateOne({'_id': BSON.ObjectId(`${id}`)},{
       "$set" : {
         processed: true
       }
     });
   })
   .catch((error) => {
     console.log('Error sending message:', error);
 });
};
```

Example of a push notification to a user:

![

## Repository

The complete code for both the App Services App as well as for the iOS application can be found in a dedicated GitHub repository.

If you have found this tutorial useful, let me know so I can continue to add more information as well as step-by-step videos on how to do this.

And if you’re as excited about Atlas App Services as I am, create your first free App today!