iOS: the Art of User Notifications scheduling
Since I launched Rosta two years ago, users have been asking for two missing features mainly:
- Add Notes on calendar days
- Alerts/Notifications on shift changes
I released a very simple implementation of notes a couple of months ago (at the moment a descent 5% of DAU are adding notes every day); on the last two Mondays I’ve been sketching the code that will enable Alerts.
The input
The user Roster (Work Duty) is represented by:
- A work shift sequence. For example:
M,M,M,X,X,A,A,A,X,X
- The first working day i.e. when the sequence start. For example:
12/2/2019
(Which at the time being, It would be yesterday)
The expected output
Each day before a shift change the app should remind the user: Tomorrow your shift is changing to <New Shift>
The triggered notifications on December 2019 would be:
Date | Notification |
---|---|
12/4/2019 | Tomorrow shift changes to Free Shift |
12/6/2019 | Tomorrow shift changes to Afternoon Shift |
12/9/2019 | Tomorrow shift changes to Free Shift |
12/11/2019 | Tomorrow shift changes to Morning Shift |
12/14/2019 | Tomorrow shift changes to Free Shift |
12/16/2019 | Tomorrow shift changes to Afternoon Shift |
12/19/2019 | Tomorrow shift changes to Free Shift |
12/21/2019 | Tomorrow shift changes to Morning Shift |
12/24/2019 | Tomorrow shift changes to Free Shift |
12/26/2019 | Tomorrow shift changes to Afternoon Shift |
12/29/2019 | Tomorrow shift changes to Free Shift |
12/31/2019 | Tomorrow shift changes to Morning Shift |
No server side
Up to this point Rosta is 100% client side. It's been working reasonably well and I haven't received any feedback asking for features that would require a server layer yet. This means Zero maintenance costs other than the DevProgram yearly fee – and my working hours, of course.
Needless to say that if – at some point – the project takes off it'd be a must to provide server side features (user data sync, mainly).
So: We have to schedule Local Notifications
The problem
As you can see, I need to schedule a local notification on a regular basis but NEITHER calendar NOR time interval dependent.
First spent some time – way more than I would’ve liked – implementing this SO suggestion: first schedule a calendar based notification and once it fires then schedule a time interval repeating one.
Take the 12/4/2019 alert as an example, the creation logic would be:
fire tomorrow -> UNCalendarNotificationTrigger AND then repeat every 10 days -> UNTimeIntervalNotificationTrigger.
Pretty straightforward, ugh?! Well, not really. Bear with me for a bit, please :)
I started to work in the implementation following a bottom-up approach:
-
address the notifications creation unit tests and implementation
- Schedule Calendar based notifications for the first round of shift changes.
- Include the expected time interval that it should be scheduled once it's fired
- When notification is received reschedule it using the userInfo time interval. At this point I was sure that there would something like a
IAppDelegate.didReceiveLocalNotification
API.
Only after I finished implementing 1. I realized that there was no APIs for 2.. The only ways the app gets to know about the notifications involve:
- user interaction – userNotificationCenter(_:didReceive:withCompletionHandler:),
- the app being on the foreground – userNotificationCenter(_:willPresent:withCompletionHandler:)
Which doesn't help that much with the notifications rescheduling I want to achieve.
Fun Sad fact: It turned out that on the SAME SO post there was another answer pointing the limitation out.
-
Lessons learned:
- don’t read just the accepted answer
- make sure that the platform hooks work as required FIRST
Background fetch
Ok, reverting changes and now trying the “background fetch” workaround/hack – TBH: not totally sure since I don’t need BF but seems to be the only way out so far ¯\_(ツ)_/¯.
Turns out that from iOS 13 background modes handling has changed. BGTask and BGTaskScheduler were introduced. So I went down the road of implementing all that support – remember lesson learned #2 here: platform hooks first. Not surprisingly the related Apple Docs were just bare bones so I ended up following this great tutorial that showed a practical approach of this new feature.
At first I thought that I would stick to the Calendarand then Time Interval approach. The problem is that there is no guarantee that the background execution will happen at a certain time. This will make the time interval computation a bit cumbersome and literally impossible to re-schedule it to fire at the appropriate hour/minutes. Let's say we want to schedule every 10 days but it should be triggered at noon we have to rely on the background task being called at least once exactly at the fire time in order to get a consistent repeating time interval.
I'll go with to Calendar triggered notifications instead. The approach will be: make sure that there are at least N notifications scheduled ahead of time. The scheduling logic will be triggered with three different events:
- When notifications are turned on by the user
- On every app launch
- When the background task is executed
Measuring
It will be important to measure how well the rescheduling logic will be performing with analytics.
How hard is getting a precise time estimate!
Once again I found myself thinking: this couldn't take more than 4 hours. And yet here I'm almost 16 hours after I started working on it. Still figuring out how to get through. One obstacle at a time!