Realm Blog
Published in

Realm Blog

Push Notifications Using Atlas App Services & iOS Realm SDK

Featured Image

Context

Firebase

Triggers

  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

Overall application logic

Diagram of registering a user and getting de FCM Token
Diagram for registering and saving FCM Tokens
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:))
}
}
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:))
}
}

Function code in Atlas

exports = function(FCMToken, operation) {

var 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");
}
};
{
"_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

Settings view where we can enable notifications to topics
Settings View screenshot
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?

  • 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.
Screenshot of the trigger configuration in the Atlas App Services UI
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;
});
Example of Push Notification to topics

Send notification to a specific device

How does it work?

Diagram of sending push notifications to a specific list of users
Screenshot of the trigger configuration in the Atlas App Services UI
  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.
{
"_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"
}
}
}
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);
});
};
Push Notification for favorite book

Repository

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Josman Pérez Expóstio

If I had to sum up my professional interests in one sentence, I could only say that I am passionate about technology.