Android and iOS Notifications in one codebase
On an Android phone, when you have a new message, email or missed call, you will be able to swipe down from the top of the screen to access the Notification Panel and see at a glance what the notification details are. When there are notifications sitting in the Notification panel, you will also see an icon at the very top of the screen, as well as a badge on the application itself.
Michael Bui wrote a wonderful plugin, flutter_local_notifications, that provides such notifications not only in Android but also in iOS. You have to consider both platforms when using this plugin, and as it happens, when using the utility class I wrote to work with this plugin. In this article, I’m going to present this class called, ScheduleNotifications.
I Like Screenshots. Click For Gists.
As always, I prefer using screenshots over gists to show concepts rather than just show code in my articles. I find them easier to work with frankly. However, you can click or tap on these screenshots to see the code they represent 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 on our computers — not on our phones. For now.
No Moving Pictures, No Social Media
There are a number of gif files in this article demonstrating aspects of the topic at hand. However, it’s said viewing such gif files is not possible when reading this article on platforms like Instagram, Facebook, etc. Please, be aware of this and maybe read this article on medium.com
Of course, I invite you to use this class which, in turn, will use the wonderful plugin by Micheal Bui to display notifications in your app. As you may know, I’ve supplied such a utility class before working with useful plugins. I’ve then presented articles on medium.com to demonstrate their use. With each article, I would take advantage of some steadfast examples to assist in the demonstration. Here, there’s no exception. In this case, I’ll use the very same example Micheal uses to demonstrate his plugin. The gif files displayed at the beginning of this article are from his example. Instead of importing his plugin, however, I’ve imported and used the utility class found in the library file, schedule_notfications.dart. Let’s go through the example code now and see how it work.
Export What You Need
Now, instead of being concern with importing the appropriate Dart file (see below) for Micheal’s plugin, simply import the file, ‘schedule_notifications.dart’;. As an example, in the screenshot below you can see the example code we’re using is suddenly inundated with a bunch of ‘undefined errors’ everywhere?! What suddenly happened to cause this?
Those errors came about because I ‘commented out’ the export command you see in the screenshot below. This export command is found in the Dart file, schedule_notifications.dart. It’s a little trick I found useful in my projects.
For example, a developer must include the plugin in their pubspec.yaml file (see below), but beyond that, there’s no need now to import the plugin’s Dart file. The file, schedule_notifications.dart, containing this utility class will ensure that all you need to work with (i.e. the plugin’s secondary classes, functions and such) will also be available to you. You need only to import the utility class and not the file, flutter_local_notifications.dart.
By design, you are to just work with and be concerned with the utility class, and so just import that file. The utility class will import the plugin itself and worry about working with that plugin. Hence, you see the only import command needed is the one highlighted by a little red arrow above.
Let’s get back to the example code Micheal uses to demonstrate his plugin. It continues on as screenshots below. Again, I’ve removed every reference to his plugin in the example code and replaced it with the utility class called, ScheduleNotifications. I’ve cut up the example code into separate screenshots, but it’s all there from top to bottom for your review. Of course, you’re free to download the whole code yourself from the available gist, flutter_notifications_example.dart.
And so, in the State object’s initState() function, we see the utility class being instantiated. The ‘Android half’ of the plugin requires that any and all Notifications you’re going to display in your app be assigned one unique id, a name, and a description — all in Strings. I’ve chosen to keep the three original choices from the example code. Each pretty self-explanatory.
Init Your Notifications
After it’s instantiated, the class object calls its init() function (see below). It’s this function that, in fact, initializes the underlying plugin as well. You can see there’s a named parameter called, onSelectNotification, taking in an anonymous function. This is the function that will fire if and when a user actually taps on the displayed notification. In this case, when a notification is tapped, a second page will be displayed. Of course, all the named parameters used in this class mirror the ones used by the plugin itself — a common attribute for a utility class. In other words, what named parameters you find here in this class, you’d also find if you were using the plugin directly. Now, you may ask yourself. Why don’t I just use the plugin directly?! Why indeed. It’s hoped by the end of this article, you’ll see why this utility class is at least a good alternative. Let’s press on.
Note, this class also involves asynchronous operations. In the screenshot above, the function, getNotificationAppLaunchDetails(), is called to assign its details to an instance variable. Such details describe if a notification was responsible for launching the very app the plugin is running in for example. Further details include the data displayed when the notification is tapped on by the user for example. Such data is called the notification’s payload.
The next screenshot shows the beginning of the long build() function used in the example code to list the many push buttons down the centre of the screen. Besides some segments of this code, I’ll display the corresponding gif file that depicts the sort of notification being codified there.
The first button listed is labelled, ‘Show plain notification with payload.’ When you press that button, the show() function highlighted below is executed. With its ‘importance’ and ‘priority’ settings, a small white window pops up near the top of the mobile screen. You see the title and body text displayed in that white window. Then, when that white window is tapped on, a second page is displayed with the payload, ‘item x’, in the title bar.
The next feature supplied by the plugin and consequently this class allows you to schedule a Notification to appear sometime in the future. To do so, you would supply a DateTime object to the function, schedule().
There may be circumstances you wish to present a notification at equal time intervals. The code below displays the notification every minute using the function, periodicallyShow().
The function, pendingNotificationRequest(), returns a List of objects describing the notifications pending for execution. Each object will have the following properties: id, title, body, and payload.
The cancelAll() function, when executed, will terminate all those pending notifications so they won’t ever be executed and displayed for example.
Let’s now walk through the utility class, ScheduleNotations. Of course, like the plugin itself, you’re required to enter three String values into the constructor when you instantiate a ScheduleNotations object. As it happens, when dealing with notations on the Android platform, you are to define and work with what is called a ‘channel’ — each identified by a unique string value. An excerpt of the Android documentation explains further:
For each channel, you can set the visual and auditory behavior that is applied to all notifications in that channel. Then, users can change these settings and decide which notification channels from your app should be intrusive or visible at all.
After you create a notification channel, you cannot change the notification behaviors — the user has complete control at that point. Though you can still change a channel’s name and description.
You should create a channel for each distinct type of notification you need to send. You can also create notification channels to reflect choices made by users of your app.
And so, the first three parameters in the constructor are required positional parameters. In turn, there is the Android channel id, name and description. The remaining parameters, some 51 in all, are named parameters and so are optional. They represent the ‘notification settings’ for both the Android and iOS platforms. Most involve the Android platform, the last 13 involve iOS.
The first named parameter, appIcon, is just that — the app’s icon. You have the option to provide a specific icon, but by default, Android’s ic_launcher is used.
The vast list of private variables is now next and most will store a default value if a specific value was not passed to the constructor. Note, I chose not to use syntactic sugar (eg. this.appIcon) and instead have them all as ‘final’ public variables. And so, it’s a long list of variables prefixed with underscores.
As you can see the ‘if null’ operator (??) is repeatedly used in the class’ constructor. This is done in this fashion because a developer could pass a list of null values for example. Whether it was intentional or not is insignificant. The ‘if null’ operator is repeatedly used to resolve most of the private variables to non-null values.
Keep scrolling. The next two screenshots are declaring the private variables.
Note the init() function, lists the same parameters found in the constructor. This means you have the option to assign values either at the constructor or at the init() function or both if necessary. In this class, the last value wins. Any values assigned at the constructor is ‘overridden’ by values assigned at the init() function. Note, values assigned at the init() function will be overridden by those passed to the specific notification functions: show(), schedule(), periodicallyShow(), etc.
It’s in the init() function that the ‘if null’ operator is again repeatedly used. It’s in this fashion that each private variable is either assigned the ‘default’ value from the constructor or assigned the value passed to the init() function.
This stretch of code updates the private variables if any parameter values were passed into the init() function. If the parameter value is null, it’s merely re-assigned its own value. Granted, a series of if statements would possibly be more efficient (In fact, I’ve decided to make that change in its gist.).
In the screenshot below, the IOSInitializationSettings() function is used to initialize the plugin settings for the iOS platform. It’s supplied default values, of course, but you’re free to pass its parameters to either the class constructor or to the init() function.
The parameter, requestBadgePermission, allows for the little red badges indicating how many notifications are currently being displayed.
The next stretch of code displayed below has the underlying plugin itself being instantiated with the function, FlutterLocalNotificationsPlugin(). An initialization object is first composed of both the Android and iOS platform settings and is delivered to the function, InitializationSettings(), and the plugin object is instantiated, it’s initialized with those settings.
The next stretch of code conveys the show() function. It is this function when called, that immediately displays the notification.
You see the show() function is returning the integer value that is the id for that notification.
The next stretch of code has the schedule() function. It takes in the required DateTime parameter.
Again, like the show() function, the schedule() function will generate an id value for you if you don’t provide one. It’ll be an integer value from the Random() function. The id returned can be used as a reference to later cancel that particular notification for example. Such an integer value is returned by all the notification funcitons: periodicallyShow(), showDailyAtTime(), showWeeklyAtDayAndTime()
The private function, _notificationDetails(), is called throughout the utility class. It configures the notification settings on both the Android and iOS platforms. It returns the object, NotificationDetails.
The private function, _notificationDetails(), will return null if the developer fails to call the init() function. The init() function, of course, does just that — it initializes the essential plugin, FlutterLocalNotificationsPlugin. I could call the init() function in these functions, but supplying some sort of conformity is another characteristic of a utility class. In this case, developers are to instantiate the class, call its init() function, call its ‘notification’ functions, and finally, call its dispose() function.
Again, in this class, the last value wins. Consequently, in this class, any values assigned at the init() function is ‘overridden’ at the show() function, the schedule() function, the periodicallyShow() function, etc.
You see the assign-if-null operator,
??=, is repeatedly used in the private function, _notificationDetails(). They’re all to assist in the ‘last one wins’ approach used in this class. You see, if the notation function didn’t provide parameter values, the init() function’s parameter values are used. If the init() function’s did not provide parameter values, the constructor’s parameter values are used. If the constructor didn’t provide parameter values, the ‘default’ values are finally used to return the object, NotificationDetails. See how that works? Why does it work that way? It’s to provide the developer the option to supply specific parameter values at any stage of the implementation. If you know me, you know I like options. That’s why.
In the first bit of code in the screenshot below, we see a ‘default’ vibration pattern is conceived if not already provided because the vibration is enabled. The class takes care of that. Finally, the Android Notification settings are passed in. You can see the required channel id, and such are used at this point.
In the screenshot below, the object, NotificationDetails, is finally instantiated with the settings for both the Android and iOS platforms. Further note, the try-catch statement is used repeatedly to catch and unexcepted errors. If such an error occurs, it’s recorded (like any utility class should) and null is instead returned from the private function, _notificationDetails().
The next few functions take up the remaining functions found in the plugin. For example, the cancel() function below takes in the integer identifier to cancel a specific notification while the cancelAll() function affects all the notification your app may have initialized. Of course, their own comments displayed below sufficiently describe their purpose.
The last bit of code is a mixin assigned to the class itself. It merely demonstrates how one could use a mixin — by supplying the error reporting capability to the class, ScheduleNotifications. In the screenshot above is the function, getError(), that’s called in every try-catch statement found in the library class. Further, below are also the getter’s used to determine if there is, in fact, an error as well as return the actual error message. Again, such a class indicates if there’s an error and records the error.
The purpose of such a class is to make life a little easier. You work with this class that then works with the underlying plugin, flutter_local_notifications. Although in this class, a great many parameters are allotted to you to use, all in a number of different functions, you don’t have to use them as most are assigned the appropriate default values. Again, such a utility class is full of repetitive code. It’s full of ‘if null’ operators and try-catch statements so that notifications are successfully displayed. And if something goes wrong, it will fail gracefully and not cause your app to crash. Yes, it’s ugly on the inside, but beautiful to work with on the outside as is the case with such a class. Take it, make it better, and share.