Notification & Alarms in Flutter

Working with the Flutter Plugin, Android_Alarm_Manager.

Know this! This plugin only works for the Android platform! I personally don’t know of any iOS equivalent. Alternatively, a month after publishing this article, I came upon a plugin that provides notifications on both the Android and iOS platforms. I, of course, wrote an article and that as well. See below.

Notifications in Flutter

Know that if you want to use the plugin described here, you must follow its readme file explicitly to set things up correctly. You’re AndroidManfest.xml should at least look something like this below:

And of course, in your pubspec.yaml file, you will need the Flutter plugin:

Now, if you’ve been following my articles, you know a good many of them are about a library Dart file containing some ‘wrapper utility class’ I wrote. More often than not, I would write such a class to work with a Flutter plugin I needed or found useful. Well, this article is no exception.

In this case, it’s for setting off alarms in your app! In my case, I found the Flutter plugin, android_alarm_manager, met the needs of a recent app I was working on and so…I made a routine to easily work with it. If you want it, take a copy, make it your own and share any improvements you make. Cool?

Now if you’ve time, please continue to read what I found I had to do to make this plugin work almost ‘foolproof’ so to easily and quickly set an alarm or any another operation to be performed ‘in the background’ at some point in time in the future while the app is running. It makes Life a little easier, and that’s a good thing. Right?

Screenshots Only. Click For Gists.

As always, I prefer using screenshots over gists to show code in my articles. I find them easier to work with, and easier to read. However, you can click/tap on them to see the code in a gist or in Github. Ironically, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program mostly on our computers; not on our phones. For now.

Let’s begin.

Other Stories by Greg Perry

My approach here is to first present this utility class in an example and demonstrate how to use it — thus taking advantage of the Flutter plugin, android_alarm_manager. In fact, I’ll use the very same example listed in the plugin's own example page. However, this example has been modified to utilize the library file presented here instead. A copy of this example is available to you as the gist, android_alarm_manager. After the example, I’ll walk through parts of the utility class itself describing at times what needed to be done to allow such a class to be used by the unyielding masses that is the general public.

Keep It Static

In this very simple example, you are to press the button displayed, and 5 seconds later, those two zeros as pictured in the screenshot below will turn into ones. Wheeee! What’s really interesting, of course, is what’s under the hood in the code.

Note, I changed the code from the original to accommodate the asynchronous operations that have to be performed before the app could actually start running. I’ve utilized a FutureBuilder widget to accomplish this. In doing so, I defined a new function called, initSettings(), to initialize the ‘Shared Preferences’ routine used to ‘remember’ the number of total alarms fired as seen on the screenshot above.

You can also see in the initSettings() function displayed below, it sets the ‘total count’ to zero if this is the first time the app has run. It’s at the start, however, that the library routine, AlarmManager, is initialized to set up the particular ‘Alarm Service’ necessary to perform notifications on an Android phone.

Let’s go further down the example code and see what makes up that little button. Note, the library routine uses the very same names for the parameters and functions that make up the underlining Flutter plugin, Android_Alarm_Manager. Better to be consistent with such things. Further, much like the plugin’s own oneShot() function, this library’s version, once called, will ‘wait’ for the specified duration before firing the specified callback routine. In the case of this modified example, the callback routine is an anonymous function that will run after a duration of 5 seconds. In the screenshot below, the app was, in fact, started up again the button pressed again indicating, thanks to Shared Preferences, that the button has been pressed twice since it was run for the very first time. Wheeee.

A closer look at that anonymous function below, and we see it is passed a single parameter of type, integer. You can guess that’s the very same id value passed to the second parameter as a random number using the Dart functions, Random().nextInt(pow(2,31)). Now why bother passing that value when it’s already passed into the parameter right beside it? We’ll get to that.

For now, you can further see below that the callback function, in turn, calls the function, _incrementCounter(). There, the current ‘total count’ of button presses is retrieved from the ‘Shared Preferences’ routine. It’s then updated by one to account for this current button press and saved back to Shared Preferences. The app’s screen is then updated with the incremented variable, _counter, using the function, setState(). Did you follow all that so far?

android_alarm_manager.dart

Keeping It Static

Unlike the original example (see below), this example is allowed to use an anonymous function and not required to specifically use a static function. Of course, you’re allowed to use a static function in this example as well — as long as it still accepts that lone integer value. However, in the original example, it must be a static function or a high-level function defined outside of any class in that library Dart file — either one, you see, is a requirement when working directly with the Flutter plugin, Android_Alarm_Manager.

However, if you’ve been following my articles, you know I like options. It’s all about having options with me. I’ve written this utility class to be more accomodating — to allow for anonymous functions for example. With such an arrangement, therefore, passing that id integer value as a parameter indeed allows the function to be a Static function, a high-level function, or an anonymous function. Options! Again, screenshots of the original example follow below:

Let’s turn to the utility class, AlarmManager, itself. Again, it’s designed to work with the plugin. And again, it’s many parameters are the very same parameter used by the plugin and so are passed to that plugin —but not before some extensive parameter testing for valid values. Another necessary trait of such utility classes. It does all the work so you don’t have to. Right?

In the screenshot below, is the first part of this utility class. In its static function, init(), we see the plugin is indeed initialized. Note, any unfortunate errors that may occur in the attempt to initialize will be caught in the try-catch statement. Utility classes need to do that too. Next in the init() function, there’s the ‘helper’ class, _Callback, which is called to initialize the necessary means for the app to communicate with the separate Isolate used by the Alarm Service in the background.

Lastly, you can see below that any and all parameter values not null are assigned to the specific and also Static properties that make up the utility class, AlarmManager. A lot of Static going on around here isn’t there.

alarm_manager.dart

You’ll find much of the properties and functions that make up this class are Static. The choice to use Static members in a class should be well-tempered, however. For example, since the init() function is Static, that means it can be called anywhere, anytime and any number of times. That fact requires some additional consideration. In this instance, the very first one-line statement in the screenshot above is an if statement: ’if (_init) return _init;’. It was necessary when writing this init() function. With that, you can now call that function as many times as you like. Regardless, the necessary services and plugins are only initialized with the very first call. And so, in a team of developers, for example, if the call to the init() function is mistakenly made more than once, there’s no harm done. Another desirable characteristic of a utility class. See what I’m doing here? Making it sorta ‘foolproof.’ Right?

Init Your Settings

By the way, with those parameters being passed along to those static variables, that means you’ve got more options in our example. When the init() function is called, the settings could have instead been specified then and there. Doing so would allow any and all subsequent calls to the functions, oneShot(), oneShotAt(), and periodic(), to use those settings if not explicitly providing their own. I’ve demonstrated this below. You can see the differences made in the example code if the additional parameters in the init() function were used instead. That just leaves the ‘oneShot’ function call with its duration, its id, and the necessary callback function. Makes the code a little cleaner. Options!

DDDon’t place init() in a build() function! It would appear the Flutter plugin’s own init() function that’s called, AndroidAlarmManager.initialize();, is prone to causing side-effects or issues. In some cases, it will initiate a rebuild (much like calling the setState() function). That’s why my utility class has a separate init() function. It’s preferable it be called near the start of your app — in a FutureBuilder widget with MaterialApp for example. See for yourself and, in your modified example, try commenting out the AlarmManger.init() function from initSettings() and place it instead right before its oneShot() function (see below.) Your example will then begin to encounter errors.

android_alarm_manager.dart

Take Your oneShot

ok, back to the utility class. In the oneShot() function, the first three ‘required’ parameter values are testing for validity and passed along to the Plugin’s own oneShot() function. All except the ‘Function’ parameter, callback. Instead, that is added to a static Map object uniquely identified by the provided integer id using the following command, _Callback.oneShots[id] = callback. We’ll get back to that soon enough. Lastly, you notice there is a call to the static function, oneShot(), also found in that helper class, _Callback. It stands in as the necessary ‘static function’ to be used by the plugin. This leaves the rest of the parameter values to take in those many Static variables using the null-coalescing operator, ??. The operator is used so when an explicit parameter is not passed, the values in those Static variables are used instead. Get it? Those static variables are all be initialized, by the way, with default values so there are no null values eventually passed to the plugin itself. Nice.

alarm_manager.dart

Take No Chances

Note, the call to the plugin itself is also enclosed in a try-catch statement. That’s because it’s a third-party program. We don’t know what could happen, and since this is a utility class, we don’t want to crash your app but instead catch any exceptions that may occur.

Further, like any good utility class, this class has the means for the developer ‘to test’ if the operation was successful or not. If not, any Exception that may have occurred is recorded so the developer could then handle it. This is demonstrated below in the modified example with the newly inserted if statement.

android_alarm_manager.dart

More Static

Further along in the utility class, AlarmManager. We see the oneShotAt() function. Again, because all these functions are Static functions, some safeguards need to be incorporated in the code. Under unfortunate circumstances, for example, the plugin may not first be initialized when this onShotAt() function is called. In other words, it’s init() function was not called first. It could happen when used by the general public. You can see in the screenshot below, such a situation is tested for with the assert() function. This is in the hope a developer will catch such a mistake while they're in development. In production, it’s caught by that if statement that follows the assert() function.

Note, this oneShotAt() function has its own Map object to store the passed in ‘callback’ function, and it has its own static function, _Callback.onShatAt(), to be passed to the plugin’s own oneShotAt() function. This all implies, by the way, you can call these functions any number of times in your app scheduling any number of operations to occur in the future. Of course, each must be assigned its own unique id value remember. Otherwise, any operation already scheduled will be overwritten with a new one if it happens to use the same id value. That’s the point when using unique id’s. Right?

However, all this also implies you can use the same id, but between the three different functions, oneShot(), oneShotAt() and periodic() separately. Remember, they have their own separate Map objects and Static functions. This fact served me well in my recent project where the id’s used were the very values found in the primary fields of the resident database. Options, baby! Love it!

alarm_manager.dart

A Plugin Peek

A quick peek now at the Flutter plugin’s own oneShot() and oneShotAt() functions, and you can see that its oneShot() function, in fact, merely passes on its parameter along to its oneShotAt() counterpart. Note, the ‘CallbackHandle’ object you see in the screenshot below comes from the function, _getCallbackHandle(), which, in turn, called Flutter’s framework function, PluginUtilities.getCallbackHandle(callback). This operation ‘tears-off’ a copy of the callback function so to have access to it and call such a function in the Isolate running in the background. I’ll get back to that as well.

The Callback Operation

Let’s continue on for now and take a look at the ‘helper class,’ _Callback, in the library file. You can see below those Map objects that are adding in the Callback function objects are defined in this class as Static properties. This class also has an init() function and it’s called in the AlarmManger’s own init() function. It’s in this init() function where a ‘port of communication’ is registered with three specific name identifiers. The port is used by the background Isolate to communicate with the foreground Isolate by passing messages to it. Guess what the values of those names identifiers are? They appear in the screenshot below stored in the variables, _oneShot, _oneShotAt, and _periodic.

alarm_manager.dart

As the name implies, isolates, are separate segments of memory
that are by design, well,…isolated. There’s no sharing of memory. There is only the passing of messages between Isolates. The content of such a message can be a primitive value (null, num, bool, double, String), an instance of a SendPort object, a List object or a Map object with any of those primitive values first mentioned.

Listen In The Background

The port is further assigned a ‘listener’ to react if and when a message is received, in this case, by the background Isolate running the Alarm Service. The listener is in the form of an anonymous function taking in a Map object as its parameter. You can see below the Map object contains an integer value (which happens to be the id) and a String storing one of those ‘name identifiers’. The case statement determines then which ‘type’ of function is to fire. See how that works? Of course, being a utility class, it's all enclosed in a try-catch statement. For instance, we have no idea what will happen when the chosen function is run. We want to catch any Exceptions that may occur. Right?

alarm_manager.dart

Send A Message

So how is that message sent to the app running in the foreground? Well, once those name identifiers are registered above, then (see below) any of the three utility class functions, AlarmManager.oneShot(), AlarmManager.oneShotAt(), and AlarmManager.periodic(), will pass the three corresponding Static functions, _Callback.onShot(), _Callback.onShotAt() and _Callback.periodic(), directly to the Flutter plugin. Doing so will allow the background Isolate to then pass a message back to the app running in the foreground Isolate. All three types of calls are listed below.

You see, it is these three corresponding Static functions, _Callback.onShot(), _Callback.onShotAt() and _Callback.periodic(), that are ‘the bridge’ from the background Isolate to the foreground Isolate. When it’s time to set off an alarm, for example, the Alarm Service will call one of these three Static functions. Note, as it happens, it’s not the actual function defined in the foreground Isolate, but a ‘torn off copy’ of it. You’ll see some counter-intuitive behaviour because of this fact. For example, any Static variable in that function normally defined in the foreground Isolate will be null in the background Isolate. You can test this phenomenon yourself.

For instance, in our modified example, if I pressed the button a third time, we know the Map object, oneShots, has that one Function object in it to run and update the screen after five seconds, and it does just that in the screenshot of the ‘Listener’ routine below. However, in the process, that Map object is empty if accessed in the background Isolate?! How is that possible? It’s possible because it’s a copy of the Map object and not the one in the foreground Isolate. Again, Isolates can only pass ‘messages’ to each other. They don’t share memory.

Again, these three Static functions are in the helper class, _Callback, listed one after the other. They’re displayed in the screenshot below. In each, you can see the foreground Isolate is referenced using the ‘name identifiers’ and is passed a Map object from the background Isolate. Note, the conditional member access operator, ?. , is used in case the lookup operation returns null. It’ll do so if the name doesn’t exist for example. It’s not likely to ever happen since this is all internalized code, but being a utility class, we don’t take any chances. Right?

All this is at the end of the library file, and it’s here where we finally see the value of those ‘name identifiers’ in the variables, _oneShot, _oneShotAt, and _periodic. They’re each named after their corresponding function type. Not very imaginative, but it makes sense. We also see they’re high-level variables defined outside a class or high-level function. In fact, they’re constant variables with the keyword, const. Like final variables, const variables are initialized only once and cannot then be changed. Unlike final variables, they’re defined when the app’s compiled. Hence, for our needs here, they’re available to even background Isolates. Nice.

alarm_manager.dart

If you don’t have a handle on all this Isolate stuff. Don’t worry about it right now. That’s what the utility class is for. Unlike the original plugin example code, you don’t have to worry about the setting up of ‘ports of communication’ or when to use a Static or a high-level function or variable. That’s why I wrote this class in the first place — so I don’t have to worry about all that stuff either. That’s why such utility classes are written at all — so they can be used over and over again by our many apps we all will be writing in the future. Right?

Cheers.

→ Other Stories by Greg Perry

DECODE Flutter on YouTube

Freelance Developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store