Implementing smart push notifications with location

Gabriel Cartier
Samsao
Published in
8 min readMay 24, 2016
photo credit Fast Company

In one of our latest client project, we had to develop a system for the push notifications to be triggered when a nearby retailer creates a deal. We spent some time trying to figure out the best solution so here’s our findings.

The problem

The initial requirements were that if a user is nearby a retailer and that creates a new deal, the user will receive a push notification letting him know about that deal. Since it was the first version of the application, the idea was to engage the user as much as possible by letting him know about the latest deals around him.

The solutions

Tracking user location in background

Probably the easiest way to implement this would be to add location tracking and save the latest location and use it once the notification is received. This would require to implement some caching system (NSUserDefaults, CoreData, Realm, etc.) to be able to compare the deal’s location with the latest retrieved location and see if the user is (or was) within range. Unfortunately, this required to ask for more permissions for the location tracking and also would drain more battery. This solution was thus discarded.

  • The pros are the ease of the implementation and the robustness of the system.
  • The cons are that it requires extra permission and it may drain more battery. It also requires the app to be in background to process the notification in a background task.

Saving latest location on the server

Another potential solution would be to save the user location to the backend and use that to check if we send the push notification to the user or not. We could easily think of a solution where the last location is silently sent on every network call or at a regular interval (i.e. every 30 minutes). In our case, since we developed a product MVP, to keep things as lean as possible and to increase the development time, we used (the now defunct) Parse as our backend service. The gain in time and money of using Parse is significant, but you have to pay extra attention to make sure you keep your network requests as little as possible (this should be a general rule anytime you use a backend ;) ). On the other hand, being able to know if the user was last close to a new deal can reduce the number of push notifications you send which can be a limiting factor with Parse. This solution was finally discarded because of the extra network bandwidth and the precision problem.

  • The pros of this solution are that notifications can always be received (no processing is required) and you can better target your notification audience.
  • The cons are that you may have to send extra information and use more network bandwidth. It will also increase the battery usage of your app because you need to send the location at frequent intervals and finally you are not 100% sure if the last location is still valid.

The “Lukasz” version

When writing this post, I asked our team to review before posting it. Our good friend came up with a pretty clever solution that I thought was worth mentioning. At that time, we didn’t think of it but it could solve some of the issues of our implemented solution. The idea is to define a geofence around the businesses. The list of businesses could be fetched when outdated to make sure you always get the latest version. Once you have all the geofences, you can set them to trigger an in app background task when entering and exiting the geofences. You send your entering/exiting of the geofences to the server so that the data is saved.

  • The pros of this solution is that it abstracts the logic of being close with a geofence. It doesn’t need to be as precise too. Also, the request would be minimal because you’d only let the server know when you walk in and out of a region. Waking up the phone to send a request of exiting/entering a geofence is still smaller in energy than fetching the location.
  • The cons are that the solution is a bit more complex to implement. It requires a specific logic to keep the businesses list updated and also requires geofencing logic (although not so hard in the last iOS versions). It is not so scalable too since you might end up with too many geofence areas when you have a lot of businesses which might trigger a lot of requests.

The final solution

Our third and final solution was to send a push notification to everyone when a new deal is created and let the device analyze if it’s close enough from the deal to display the notification. We have to trick the system to be able to achieve that. We’ll go deeper in the explanation and the problems we encountered, but the reasons why we chose this solution are because we limit the network usage and we can be 100% sure that the user is within range of the deal. That was a requirement since we don’t want to send push notifications to user that are not nearby. As we all know, push notifications can be beneficials for your application, but like everything, too many push notifications is not a good strategy.

  • The pros of this solution is that we have the certainty that the user is near the deal. It is also minimalistic on battery usage. It also does not require extra permissions from the user.
  • The cons are that the app needs to be in background for the push to be received as it requires processing. It is also not a straight forward implementation, although that’s the fun part of it ;). One important thing to note is also that this is not really scalable because when a lot of deals are created, it will often ask for background processing + GPS location fetching which might end up in battery drainage. It was OK for our solution since the application was not on a big enough scale yet.

The implementation

Once we had decided on the solution to choose, we started developing the solution. The idea is that we handle the push notification within a background task and we validate that the distance is correct and if so, we send a local notification. Here’s a diagram to better understand the behaviour:

Our preferred implementation for handling push notification is to have a NotificationHandler class that receives any kind of notifications and dispatch them to the proper components. Once a push notification is received, we dispatch it to the handler like so:

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject: AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {  
NotificationHelper.handlePushNotification(userInfo, isAppLaunched: UIApplication.sharedApplication().applicationState == .Active, completionHandler: completionHandler)
}

As you can see, we simply dispatch the notification to the handler that will manage it by checking the type. The application state is used to actually either trigger a local notification or simply display an alert view in the app. We have multiple types of notification in the app but the one we want to focus on is the nearby deal. Here’s the implementation of the handler:

private static func displayNewDealNotificationIfUserIsNearby(userInfo: NSDictionary, isAppLaunched: Bool, completionHandler: ((UIBackgroundFetchResult) -> Void)? = nil) {  
guard let completionHandler = completionHandler else {
return
}
//The 20 seconds here is because OS limits background to 30 seconds
LocationHelper.sharedInstance().retrieveCurrentPosition(20) { (status, location) in
guard location != nil else {
// Error fetching location
completionHandler(UIBackgroundFetchResult.Failed)
return
}
guard let latitude = userInfo["latitude"] as? Double, let longitude = userInfo["longitude"] as? Double else {
// Improper format
completionHandler(UIBackgroundFetchResult.Failed)
return
}
let businessCoordinates = PFGeoPoint(latitude: latitude, longitude: longitude)

// Check for the distance between the new deal and the user
let distanceBetweenUserAndDeal = LocationHelper.sharedInstance().distanceBetweenUserAndLocation(businessCoordinates.clLocation())
guard distanceBetweenUserAndDeal < kMaximumDistanceBetweenUserAndDeal else {
// Deal is not nearby
completionHandler(UIBackgroundFetchResult.NoData)
return
}
if isAppLaunched {
self.displayAlertForDeal(userInfo["id"] as? String)
} else {
self.sendLocalNotificationForNewDeal(userInfo)
}
completionHandler(UIBackgroundFetchResult.NewData)
}

As you can see, the method is pretty straight forward, although there are some important stuff to be careful about.

  • In our case, we want to do some processing when an app is received and for this, we need to properly call the completionHandler being passes to the AppDelegate. Not only should you call the handler, but try to respect the syntax as much as possible. If there is no data, an error or a success, simply call the handler with the proper UIBackgroundFetchResult enum type.
  • As you can see, we use a helper for the location which receives a timeout as a parameter. This part is important because you want to limit the location fetching process. One big headache we had was that if we increased the delay too much, the application would simply not receive the notification. We were able to debug the problem by inspecting the crash logs of our devices and seeing something like this:
  • As you can see, the OS simply throws an exception if your app takes too much time in the background. This is not clearly stated anywhere in the Apple documentation and caused a lot of problems on our side. One of the problem is that the GPS actually takes a lot of time in the background. Again, not stated in the documentation.
  • Finally, the last thing to take care of, is to actually either show an alert of send a local notification if the application is not Active. Once the local notification is sent and the user opens, you can simply handle it as you would handle a normal notification.

Future improvements

Although the current implementation fulfill the requirements, an optimal implementation could allow the user to choose the kind of food or his favorite retailers from which he wants to receive a notification. This would make sure that the user is only notified about deals that he will have a specific interest in.
We could also think of a logic to minimize the push sent to the users by pre-sorting the users that could potentially receive the push. For example, we could think of a city field where we only send the notifications to the user in the same city as where the deal was created.
Also, we could implement some throttling logic to make sure the user is not spammed with notifications. On the first iteration, this was not really a problem since there was not so many deals created on the platform.

We are pretty satisfied with the solution we came up with. Although it required a bit of tricking the system to get it working, it ends up being pretty robust.

Don’t hesitate to comment or share your experience in the comment section below.

If you want to know more about our digital agency, please visit our website or reach us here.

--

--

Gabriel Cartier
Samsao
Editor for

Love technology. On a quest to build a product.