Scheduling Notifications on Android with WorkManager
As I promised in my previous post, I’m back with a quick, no-nonsense guide to scheduling notifications on Android. If you haven’t, I strongly recommend you read the previous article before going into this one:
https://www.dhirubhai.net/pulse/building-notifications-android-o-adrian-tache/
Scheduling notifications is a strange territory, and over the years there have been a number of tools that people used for it, such as AlarmManager, JobScheduler, Firebase JobDispatcher (for backwards compatibility), etc. They all mostly work, but they have certain issues and limitations, such as having notifications stop when the app is force closed or the device is restarted.
To resolve this mess, Google has released the WorkManager as part of their Architecture Components, and it’s super enjoyable to use! In this article I’ll show you a simple way of using it.
The absolute first thing you need to do is to add the Work Architecture component to your build.gradle as per these instructions:
There are three main steps to scheduling a notification:
1. Building the Worker Class
The first step is to extend the Worker class and let it know what the actual work is that it should be doing when it triggers. We do this by overriding the doWork() method of the Worker class and returning a WorkerResult.
public class NotifyWorker extends Worker {
@NonNull@Override
public WorkerResult doWork() {
// Method to trigger an instant notification
triggerNotification();
return WorkerResult.SUCCESS;
// (Returning RETRY tells WorkManager to try this task again// later; FAILURE says not to try again.)
}
}
In my example, we’re triggering a method that simply shows a notification, as per my previous guide. Since we don’t need to monitor this task, we just return a WorkerResult.SUCCESS.
2. Building the Work Request
The second step is to build the work request that will get passed to the WorkManager in the next step. We do this by using a OneTimeWorkRequest since we only need to trigger the notification once. Alternatively, we could use a PeriodicWorkRequest for recurring work.
//we set a tag to be able to cancel all work of this type if needed
public static final String workTag = "notificationWork";
//store DBEventID to pass it to the PendingIntent and open the appropriate event page on notification click
Data inputData = new Data.Builder().putInt(DBEventIDTag, DBEventID).build();
// we then retrieve it inside the NotifyWorker with:
// final int DBEventID = getInputData().getInt(DBEventIDTag, ERROR_VALUE);
OneTimeWorkRequest notificationWork = new OneTimeWorkRequest.Builder(NotifyWorker.class)
.setInitialDelay(calculateDelay(event.getDate()), TimeUnit.MILLISECONDS)
.setInputData(inputData)
.addTag(workTag)
.build();
In order to do this, we call the OneTimeWorkRequest.Builder and pass it the name of the custom class we’ve previously defined. We can set a number of options for this request, as well as specify Constraints to prevent the work from running if the battery or storage are low, and so on.
We have used setInitialDelay to ensure the notification is triggered on the day of the event rather than immediately. This delay is calculated by a separate method which returns the amount of milliseconds between now and when the notification should trigger (at noon on the date of the event). Please note that this delay is not always accurate! Due to how modern Android works, your work can be delayed by a number of factors, so be aware of this for very time sensitive purposes.
We have used setInputData to pass the event ID to the NotifyWorker class in order to include it in the PendingIntent to determine which event should be opened. To use it, we defined a new Data object as shown above.
Finally, we have used addTag to set a tag for this work type in order to be able to call:
WorkManager.getInstance().cancelAllWorkByTag(workTag);
to remove all work of this type from the queue in order to prevent duplicates. Another way of doing this will be explained at the next step.
3. Enqueueing the Work Request
Now that we’ve created everything we need to schedule the work, we can just ask the WorkManager to queue it to the list of active tasks from the system:
WorkManager.getInstance().enqueue(notificationWork);
//alternatively, we can use this form to determine what happens to the existing stack
//WorkManager.getInstance().beginUniqueWork(workTag, ExistingWorkPolicy.REPLACE, notificationWork);
//ExistingWorkPolicy.REPLACE - Cancel the existing sequence and replace it with the new one
//ExistingWorkPolicy.KEEP - Keep the existing sequence and ignore your new request
//ExistingWorkPolicy.APPEND - Append your new sequence to the existing one,
//running the new sequence's first task after the existing sequence's last task finishes
At this point, the WorkManager will add the work to its queue, then determine when it can run and do the work as specified in the first step. And there you go, your notifications will now trigger on time, regardless of device restarts, app force closes, and without using a bulky service.
I also recommend reading Google’s guide on using WorkManager:
Thanks for reading this article. You can connect with me on LinkedIn.