A Tale of Two Alert Schemes
So I built a nice budget app for Android and iOS. A core feature is sending you an alert when a bill is nearly due. Each platform required a very different approach to reliably schedule and trigger those notifications. This… is that story.
Scheduling stuff is hard
Have you ever received an alert from an app for something you deleted? For example, you keep getting a reminder to pay your “Doggie Daycare” bill, but you don’t even have a dog anymore? Sometimes, the only way to rid yourself of the zombie notifications is to reinstall the app. Oops. 🤦♀️
Clearly, the Devs aren’t trying to annoy you – scheduling (and rescheduling) stuff is hard. Take the “doggie daycare” example. Here’s some of what 4Ducks Budget has to keep track of when you choose to receive expense due alerts.
- User installs app: Are alerts permitted?
- User creates budget: Are alerts enabled for the budget?
- User creates expense and enables alerts: Schedule notification.
- User changes due date: Delete notification. Schedule a new notification.
- User disables notifications in app: Delete notification.
- User disables notifications for app at system level: App gets in funky notification state.
- User re-enables notifications for app at system level: Detect change, delete original notification. Schedule a new one.
- User deletes the expense: Delete the notification.
- User deletes the budget that doggie daycare was part of: Delete the notification.
- User imports a budget: Schedule all the notifications.
Plus, what about failure modes? For example:
- User is importing a budget with a number of expenses, but the import fails or the app is force-closed. Are we left with zombie notifications?
Clearly, there are a lot of things to juggle, and lots of places for things to go wrong. Let’s look at the two different approaches I took with 4Ducks Budget, starting with Android.
Android alarms for the time/complexity win
On Android, my approach was to avoid most of the above complexity entirely by only scheduling one thing – a daily alarm. How does it work?
- The alarm runs every morning.
- If we don’t have notification permissions, bail out.
- Checks to see if there are any expense-due alerts to show that day.
- Also checks for any missed alerts from the last several days (e.g. the phone was off)
- Show the alerts
It doesn’t matter if the expense due date is modified, or an entire budget is deleted, because we are using a just-in-time approach to showing the notifications. This is very 😌 ahhh…
So what about iOS?
iOS Notifications, for the time sink.
Unfortunately this approach breaks down on iOS. You can schedule background jobs an iOS - the problem is that they aren’t reliable. Why not?
- As a battery-preserving measure, the job likely won’t run at a specific time.
- Worse, iOS may decide not to run the job at all because users don’t open your app very much.
I think my budget app is pretty great, but it’s just not going to have the same level of engagement as Tiktok. :D
So unfortunately, I decided to take the long way around and schedule my notifications using iOS’ notification system.
My biggest fear was failure states, so I built a notification “journal” database to record the scheduled notifications. Here’s an example of how it works:
- User creates a new expense and hits Save
- Start a database transaction.
- Save the expense.
- Write the scheduled notification to the notification journal.
- (If there is an error, roll back the transaction, including the journal entry.)
- Commit the transaction.
- Schedule platform notifications for the new notification journal entries.
This adds a lot of complexity to the design, but it does help avoid a lot of failure-related bugs. Plus, it’s easy to compare the desired notification state (the journal) with the actual notification state (iOS) to suss out any scheduling bugs.
So 4Ducks Budget will be by your side, letting you know when your bills are due, through the best of times, and the worst of times.
Stay solvent my friends.