Using protocols to handle remote notifications
My last post covered how we can handle notifications with NotificationCenter using an elegant and type safe way. It can be improved, of course, and this guy did an awesome job to solve the same problem.
If you didn't read my last post, I strongly advise to do so. We're going to extend that solution to handle push notifications. Also, I won’t cover how to enable or register an app for remote notifications.
Let's get started. The first thing you need to know is, push notifications are handled differently base on your app state:
- If the app is running or in background, iOS calls the
application(_:didReceiveRemoteNotification:fetchCompletionHandler:)method (or related). - If the app is not running, the notification is handled by
application(_:didFinishLaunchingWithOptions:)using theUIApplicationLaunchOptionsKey.remoteNotificationkey onoptionsdictionary (remember this for later).
The idea here is to improve our NotificationType and use the NotificationCenter to dispatch the remote notification inside the app. First, let's define a RemoteNotificationType protocol:
Now, let's change our openUserProfile notification to be handled as a remote notification:
Now our notification can be constructed using the remote notification payload.
Don't forget to store
titleandalertattributes onuserInfoproperty and restore them back on theinit?(notification:)constructor.
Most apps have different actions based on a push type. If you're developing a social app, you may need to define a push to open the user profile when someone receives a friend request, or open a chat when a user receives a message. To handle these cases, I usually put a type parameter inside the push payload matching the notification name but you can use other strategies as well. Let's define a notification factory function to create the object based on it's type.
I usually put this function in AppDelegate.swift as fileprivate. Now we can handle and post our remote notification using the NotificationCenter overriding the method application(_:didReceiveRemoteNotification:fetchCompletionHandler:):
Everything seems to work fine but there is a catch: this method is called only when the app is running or in background. To handle it on fresh start, add this at the beginning of your application(_:didFinishLaunchingWithOptions:) method:
We check if the app has been launched as a result of a remote notification using the .remoteNotification key and get the userInfo payload, the rest is exactly the same.
But it won't work as expected. 😩
When your app is on a fresh start,
application(_:didFinishLaunchingWithOptions:)will be the first method called. Your notification will be posted before any handler listens to it. You will be yelling to nobody.
To solve this problem we need to post the notification at a later time, when the app is fully loaded. Here's the code:
The idea here is pretty simple; while the NotificationCenter is not active, save all notifications; when it becomes active, post them at the same order they came. Let's dig a little bit in this code:
- Lines 5–8: Define the objective-c keys to store values on
NotificationCenterobject. We know that Swift doesn't allow us to define stored properties on extensions but we can use theObjectiveCAPI as a hack to do it. - Lines 10–30: A private extension with all logic needed to handle deferred notifications. The first part defines an array where the objects will be stored and the second a method to post them.
- Lines 32–57: This is the public API we want to define: an
activeproperty to tell when theNotificationCentershould store or post notifications and a new method to handle this new feature.
Now we just need to replace the line NotificationCenter.default.post(notification) to NotificationCenter.default.post(deferred: notification) in AppDelegate and set active = true when the app is ready. I usually set this property on the viewWillAppear method of my main view controller.
Now it works! 🎉 This solution is generic, scalable and you can use in any app. That's it for today, see you next time. 😉
