May 18, 2020

CometChat tutorial: Adding Push Notifications to your iOS Chat App

Prerequisites

Before you get started, there are a couple of important things you need to show push notifications on an iOS device:

  • First of all, you need a physical iOS device. You won't be able to fully test push notifications in the Simulator.
  • Secondly, you'll need an app signed by an Apple Developer account that's part of the Apple Developer program. In other words, you'll need a paid developer account. Otherwise, your app won't be able to receive push notifications.
  • Finally, this tutorial assumes you already have a head start – you'll need a working CometChat iOS chat app.  If you want to learn how to create a chat app, then take a look at our one-on-one iOS chat app tutorial, or our group iOS chat app tutorial.

Introduction

No iOS chat app is fully complete without the ability to show push notifications. Whether it's finding a buyer for the things you're selling, a potential date, or simply a friend who wants to get in touch, the messages your user receives are important. You don't want them to have to manually open your app in order to see if they have a new message. Or, even worse, to miss their next date or buyer.


You can streamline adding push notifications to a CometChat-powered iOS app, by using CometChat Extensions. CometChat Extensions are commonly-requested features that can make your app stand out from the competition, as well as provide a better experience to your users. There's a bunch of different extensions to choose from: Email and SMSnotifications, link previews, translation, image and message moderation, sentiment analysis and other useful features. In this tutorial, we'll focus on the Push Notifications extension.



Before you get started, there are a couple of important things you need to show push notifications on an iOS device:


  • First of all, you need a physical iOS device. You won't be able to fully test push notifications in the Simulator.
  • Secondly, you'll need an app signed by an Apple Developer account that's part of the Apple Developer program. In other words, you'll need a paid developer account. Otherwise, your app won't be able to receive push notifications.
  • Finally, this tutorial assumes you already have a head start – you'll need a working CometChat iOS chat app.  If you want to learn how to create a chat app, take a look at our one-on-one iOS chat app tutorial, or our group iOS chat app tutorial.


You can find the finished project on GitHub.

Setting up Firebase

Usually, if you wanted to add push notifications to your chat app, then you'd first need a server-side component that sends push notifications to Apple's Push Notification service (APNs). APNs will then broadcast the notification to the user’s device. The device will read the notification and present it if the user has granted permission.


Using CometChat you can automate much of this process. Instead of sending requests to a server, CometChat can automatically send notifications to your user's device when they receive a message. All you have to do is enable the service, write a few lines of code, tell it where to send pushes and you're done!


To send notifications, CometChat uses Firebase Cloud Messaging, a cross-platform solution from Google that lets you send and receive push notifications to both APNs and their Android equivalent. To get started with Firebase, head over to the Firebase Console and create an account if you don't already have one.


Once you’ve created your account, create a new project by clicking the Create Project button. Give it a cool name and click Continue. When asked about Firebase Analytics, uncheck Enable Firebase Analytics – you won't be needing that for push notifications.


Once you’ve created your project, Firebase will present you with your Project Overview. Click the iOS icon to create a new iOS app within your Firebase project.


On the next screen, enter your app's bundle ID and click Register app.



You can find your bundle ID by opening your app in Xcode, clicking your project in the Project navigator, and then looking under Bundle ID when you click your main target.



Once your app is registered, you'll be able to download the config file called GoogleService-Info.plist. Download it, drag it over to Xcode and drop it into the root folder of your project.


Select Copy items if needed and make sure your main app target is selected.

Also, make sure that the file is named exactly GoogleService-Info.plist. If you download it multiple times it might have something like “(2)” appended.


Firebase uses GoogleService-Info.plist to connect your app with the Firebase project you just created. This file contains identifying but non-secret information about your project, like your Firebase account and app IDs, links to your Firebase databases, etc.


    Note: If you change your bundle ID this config file will no longer be valid. You'll have to set up a new app on Firebase and download a new config file.


Adding an Authentication Key


You're almost finished configuring Firebase! There's only one thing left: Giving Firebase permission to send notifications to your users. You can do this by uploading your Authentication Key to Firebase. You'll get this key from none other than Apple themselves.


Head over to the Apple Developer Center and, once you log in, open the Account section and take a look at the sidebar. Open Certificates, IDs and Profiles and then the Keys section.



Press the + button to add a new key. As the name, enter Push Notifications Key (or any other name that you like). From the list of services, select Apple Push Notifications service (APNs) and then click Continue.



Click the Download button to download the key.


    Note: You can only download the key once! Make sure to store it in a safe, easy-to-remember location.


This is a key you got from Apple that allows you, or anyone holding the key, to send push notifications to your apps. Next, you'll give this key to Firebase so they send notifications for you.


Head back to the Firebase Console, open your project, click the gear icon next to Project Overview in the sidebar, andthen select Project settings. In the Cloud Messaging tab, scroll down to iOS app configuration. Under APNs Authentication Key, click Upload.


A new window will pop up requesting information about your key. First, select the key you downloaded. Your key will be named something like AuthKey_123ABC456D.p8, the string of capital letters and numbers between AuthKey_ and .p8is your Key ID enter this value.


Next, for the Team ID, head back to the Apple Developer Portal and click Membership in the sidebar. You'll find your Team ID there, so copy the value in the Team ID field into Firebase. Click Upload to upload your key to Firebase.


Now Firebase has a key it can use to send push notifications on your behalf. That's it for your Firebase configuration! Now you have to hook up Firebase and CometChat.

Setting up CometChat

Next, you'll enable the push notifications extension on CometChat. Open your CometChat Pro Dashboard and selectyour app. In the sidebar, click Extensions, select Push Notification and click Add Extension. Now you’ve successfully added push notifications to your CometChat app, but you still need to connect Firebase to CometChat.


Back in the Firebase Console, open the Project settings again and copy the Server key in the Cloud Messaging tab. It will be a huge string of letters, numbers and symbols.


Back in the CometChat dashboard, under Extensions, open the Installed tab and under Push Notification, selectSettings. Then, paste your Firebase server key under FCM Server Key and hit Save.



Now CometChat knows how to talk to Firebase. All that's left is to add support for push notifications to your iOS app!

Setting up your app


There are a couple of steps you need to take to enable push notifications in your app.


Declaring capabilities


You’ll start with the project settings. First, you'll need to declare that your app is capable of displaying push notifications. Open your project in Xcode. Click on your project in the Project navigator and open the Signing & Capabilities tab.



Click on the + Capability button, search for and click Push Notifications and then add it as a capability.



Next, add another capability called Background Modes. Check Remote notifications from the list of Modes and keepthe others unchecked.

The Background Modes capability lets your app show push notifications, as well as handle them while the app is in the background.


Whew, that's a lot of clicking! It's time to roll up your sleeves and do what you do best: write some code.


Asking for push notifications permission


Before you can show push notifications, you have to ask the user's permission. Navigate to AppDelegate.swift and add the following code to the bottom of {% c-line %}application(_:didFinishLaunchingWithOptions:){% c-line-end %}, before the return statement:


{% c-block %}
UNUserNotificationCenter.current().requestAuthorization(
 options: [.badge, .sound, .alert],
 completionHandler: { granted, _ in
   guard granted else { return }
   DispatchQueue.main.async {
     application.registerForRemoteNotifications()
   }
})
{% c-block-end %}


UNUserNotificationCenter is your app's personal push notifications tour guide, which is responsible for requesting permissions, as well as handling incoming notifications. In the above code, you're requesting permission to show notifications with a badge, sound and an alert (the notifications you see on the device).


When the user grants or denies the permission, UNUserNotificationCenter calls the completion handler you provided with a Boolean result and an error. The result will be true if the user allowed one of these permissions. A value of truedoes not guarantee that the user has enabled all the options!


If the user granted the permission, you move back to the main queue and call registerForRemoteNotifications which requests a device token from APNs. The device token is a unique string that identifies one specific device in the global sea of iOS devices. You'll notice this function doesn't have any callbacks, that’s because you get its result by implementing UIApplicationDelegate methods.


Add the following method to AppDelegate:


{% c-block language="swift" %}
func application(
 _ application: UIApplication,
 didFailToRegisterForRemoteNotificationsWithError error: Error) {
 print("Cannot register for notifications: \(error)")
}
{% c-block-end %}


application(_:didFailToRegisterForRemoteNotificationsWithError:)  gets called if the app can't register for push notifications. If you follow the steps of this push notifications tutorial, this method shouldn't get called, but in case of an error, it's good to have the error printed on the console.


Run your project on an actual device (not in the Simulator). When the app launches, you should see an alert asking for thepush notifications permission.



Make sure to press Allow – you wouldn't want to deny your own app. :)


Connecting your app with Firebase


Now that you have the user's permission to display push notifications, you need to get the notifications working. To do this, you need to install and set up the Firebase Messaging SDK.


If you're using CocoaPods, add the following pod to your Podfile:


pod 'Firebase/Messaging'


    Note: I strongly recommend you use CocoaPods for the Firebase SDKs, but if you can't then take a look at Firebase’s
    guide on installing without CocoaPods
    .


Then, navigate to your project's folder in Terminal and install your new pod:


pod install


After a while, CocoaPods will add the Firebase Messaging SDK to your project.

Now that you have the SDK, it's time to set it up in your app. Head back to AppDelegate.swift and add a new import to this file:


import Firebase


Next, add the following line to the top of application(_:didFinishLaunchingWithOptions:):


FirebaseApp.configure()


This function call will load the GoogleService-Info.plist file that you added earlier in the tutorial, and configure the Firebase SDK based on the information it finds in this file. If something goes wrong during the configuration thenFirebase will usually print an error in the console.


That's all you need to configure Firebase! You'll notice you implemented application(_:didFailToRegisterForRemoteNotificationsWithError:), but you never implemented a method that handles successful registration – that's because Firebase does that for you. Firebase uses a technique called method swizzling to implement a couple of delegate methods for you. When iOS gives your app a device token, Firebase will automatically send that token to its servers so it knows which device to send notifications to.


Now you're all set up to start receiving notifications! I bet you can't wait to hear your phone buzz.

Receiving push notifications

Firebase Messaging groups notifications into topics. You can think of topics as group chats. Every user subscribes to a list of topics and they receive every notification that’s sent to those topics.


The Firebase Messaging topics are also how CometChat interacts with Firebase. CometChat will create a specific topic for each user and group in your app. Once you subscribe to a user topic, you'll automatically receive all the user's incoming messages as push notifications.


You'll subscribe to a topic after the user logs into your app. In the supplied project, the code for logging in is in the loginfunction inside ChatService.swift. In your app, find where you call CometChat.login(UID:apiKey:onSuccess:onError:).


First, add a new import to the file:


import FirebaseMessaging


Next, add the following code to the callback of the login function call:


CometChat.login(

 UID: email,

 apiKey: Constants.cometChatAPIKey,

 onSuccess: { [weak self] cometChatUser in

   // Add the following lines to the callback:

   DispatchQueue.main.async {

     Messaging.messaging().subscribe(

       toTopic: "\(Constants.cometChatAppID)_user_\(cometChatUser.uid!)_ios")

   }

   // Continue handling logging in...

 },

 onError: { error in

   print("Error logging in:")

   print(error.errorDescription)

   DispatchQueue.main.async {

     onComplete(.failure("Error logging in"))

   }

 })


Each Firebase topic has an associated topic string that you subscribe to. In this case, you're subscribing to a topic with the name YOUR-APP-ID_user_USER-ID_ios. This string is important because CometChat creates topics in this format for each user in your app. You can also subscribe to group chats using the format YOUR-APP-ID_group_GROUP-ID_ios.


CometChat sends all incoming messages to the user or the group topic as push notifications. Once you subscribe a device to this topic, Firebase will send those push notifications to the device.


Run the app on a device and log in as superhero1 (or one of your users) and then lock the device, so that your app is no longer showing. Next, run the app on another device (or a Simulator) and log in as superhero2 (or a different user). From the superhero2 device, send a message to Iron Man (superhero1).


You should see your screen light up with a new incoming push notification! Without much code, you have a chat app that automatically sends push notifications to your users' devices. Pretty cool!

Showing push notifications while the app is active

All is good when the screen is locked, but open your app and you'll notice an issue: When a push notification comes in while the app is active, nothing happens! Don't worry – this is the default iOS behavior and it's easy to change.


The push notifications are still being sent to your app, they're just silenced. In this section, you'll make them appear even when the app is active.


Head back to AppDelegate.swift and add the following line to the top of application(_:didFinishLaunchingWithOptions:):


UNUserNotificationCenter.current().delegate = self


Next, add the following extension to the bottom of the file:


extension AppDelegate: UNUserNotificationCenterDelegate {

 func userNotificationCenter(

   _ center: UNUserNotificationCenter,

   willPresent notification: UNNotification,

   withCompletionHandler completionHandler:

   @escaping (UNNotificationPresentationOptions) -> Void) {


   completionHandler([.sound, .alert])

 }

}


That's a pretty big method name for just one line of code! This method gets called when a push notification comes in while the app is in the foreground, so you have a chance to read and react to the incoming push notification through a completion handler.


By calling the handler with different options you can control the notification’s appearance. For instance, you can provide only .sound so the notification plays a sound, but doesn’t show an alert. In this case, you're enabling both sounds and alerts so the notification shows up normally.

Run the project again and do the same dance of sending a notification from superhero2 to superhero1.


This time, you'll see your notification come in even when the app is currently in the foreground.


Hiding notifications on specific screens


Now that you're showing notifications while the app is in the foreground, it might be a good idea not to show push notifications from people the user is already chatting with. If they're already inside the chat screen, talking to Iron Man, then it doesn't make sense to show new messages from Iron Man as push notifications.


You'll enable or disable push notifications by calling the completion handler of the method you just implemented with different parameters based on the currently opened screen and the ID of the user that sent the message. If your user is currently inside a chat screen and the push notification's user ID matches the person they're chatting with, then you won't show the notification.


First, you'll have to grab the user ID from the push notification. Change the contents of userNotificationCenter(_:willPresent:withCompletionHandler:) to the following:


guard

 // 1

 let rawMessage = notification.request.content.userInfo["message"] as? String,

 // 2

 let messageData = rawMessage.data(using: .utf8),

 // 3

 let messageJSON = try? JSONSerialization.jsonObject(

   with: messageData, options: []) as? [String: Any],

 // 4

 let message = CometChat.processMessage(messageJSON).0

else {

 return

}

// 5

let notificationSender = message.senderUid


Here's a breakdown of what's happening in the above code:


  1. Each push notification can hold a bit of user-defined information. With CometChat, the notification will contain a JSON string with information about the message. First, you grab this string.
  2. Then, you convert the string to Data.
  3. Next, you convert the Data to a dictionary using Foundation's JSONSerialization.
  4. Once you have the dictionary, you can use CometChat's helper function to get a CometChat Message instance from the dictionary.
  5. Once you have the message, you can grab the sender’s ID.


Now that you have the sender's ID, it's time to get the chat screen and see who the logged-in user is chatting with. Add this code to the method:


// 1

let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow })


// 2

if let navigationController = keyWindow?.rootViewController as? UINavigationController,

 let chatViewController = navigationController.topViewController as? ChatViewController {


 // 3

 let currentReceiver = chatViewController.receiver.id

 if currentReceiver == notificationSender {

   completionHandler([])

   return

 }

}


// 4

completionHandler([.sound, .alert])


This is another large block of code, so let's break this one down, too:


  1. First, you grab the key window of your application. Usually, this will be the only window of your app.
  2. Next, grab your root navigation controller and check if its top-most view controller is an instance of ChatViewController.
  3. If it is, then grab the receiver ID, which is the ID of the person who the current user is chatting with. If this ID matches the one that sent the message in the notification, then you'll call the completion handler with an empty list of options, which will silence the notification.
  4. If the user isn't chatting with the sender, then show the notification normally.


Run your project one more time and check the behavior of the push notifications. If you're talking to a person and a new message comes in, then you'll notice you don't receive a push notification. However, if you leave the screen or background the app, then notifications should start showing up!


This strikes a balance between showing notifications when they're useful but not annoying the user with too many chimes, alerts and buzzes.

Conclusion

You can find the finished project on GitHub.


With a couple of clicks and a few lines of code, you connected APNs, Firebase and CometChat to add push notifications to your iOS app. You learned how Firebase Messaging works, how to get an authentication key from Apple, how to connect Firebase with CometChat, how to request push notifications permission, as well as how to display push notifications both in the background and while the app is active. That's a lot of learning for one humble tutorial. :)


Don't stop at push notifications! CometChat offers a lot of other extensions, like email and SMS notifications,  link previews, translation, image and message moderation and other features that will let you stay ahead of your competition and bring more usability to your users. Take a look at some other extensions you can add to your app.


If you want to learn more about push notifications, here's some more reading to get you started:



I hope this iOS push notifications tutorial will help you create an amazing chat app!