We are going to discuss how to display notifications using the flutter_local_notifications
plugin.
As always with these posts, you can be sure you’ve got all of the basic knowledge you need by reading my Flutter book, but not all of you like that way of learning, so I’ll list the topics which are considered prerequisites for this post.
To be able to easily follow this post, you need to know the following:
Stream
s and listening to them (StreamSubscription
);The backend for our app will be the one we wrote for the previous post in the series. The app itself will be based on that, so I highly recommend you read the last part of that post if you want to understand how we’ve got topics a fully working example app at the end of this post. One way or another, here’s the GitHub repository with the example Flutter code and, for your convenience, here’s the repo for the Node.js backend code.
In Flutter apps, you can show notifications to the user while the app is running using the flutter_local_notifications
package. It is fairly easy to use.
Before being able to use the plugin, you need to initialize it. This requires two different configuration steps: Android configuration and iOS configuration. Even though this is platform-specific, this is all done in Dart code. More specifically, plugin initialization works the following way:
FlutterLocalNotificationsPlugin notifications = FlutterLocalNotificationsPlugin();
AndroidInitializationSettings androidInit = /* ... */;
IOSInitializationSettings iOSInit = /* ... */;
var initSettings = InitializationSettings(androidInit, iOSInit);
notifications.initialize(initSettings);
Now we are going to focus on AndroidInitializationSettings
and IOSInitializationSettings
.
Android configuration requires the creation of a drawable asset to use as the app icon displayed in the notification. To do that in Android Studio, get to the android/app/src/main/res/drawable
directory in the left Project panel, right-click, and select New->Vector Asset. This will guide you through the creation of a drawable vector asset.
Take note of its name because, in order to initialize the flutter_notifications
package, you need an AndroidInitializationSettings
object, which is created in the following way:
var androidInit = AndroidInitializationSettings('name_of_the_drawable');
The configuration for iOS is at the same time simpler and harder: there is no required argument, but there are a bunch of optional ones, which are fine to be left to default values and that you can find on the official API reference for the package. By default, it will ask the user to give permission to the app to show notifications when the plugin is initialized.
This means you can get a simple IOSInitializationSettings
with default settings with
var iOSInit = IOSInitializationSettings();
The notifications can be shown with plugin.show(id, title, body, NotificationDetails);
.
title
and body
are self-explanatory, id
can just be set to 0.
NotificationDetails
is constructed with NotificationDetails(AndroidNotificationDetails android, IOSNotificationDetails iOS)
. The required (positonal) arguments for the AndroidNotificationDetails
constructor are AndroidNotificationDetails(String channelId, String channelName, String channelDescription)
, whereas the IOSNotificationDetails
constructor has no required parameters. Both take many optional named arguments, which I won’t cover here (also because I can’t be sure they will stay the same for long and we won’t use them in this tutorial), but that you should check out on the official API reference for the plugin (click here to see the options for iOS and here for those for Android).
The AndroidNotificationDetails
constructor requires all of the arguments Android requires to show notifications. More specifically, Android Oreo (8.0, API level 26) and successive releases allows users to have different settings for different notification channels originating from the same app. The channelId
can be any integer, you use the same number for notifications to be sent on the same channel and you use different numbers for different channels. channelName
and channelDescription
are names the user will use to differentiate your app’s channels, so they should be descriptive of the purpose of each notification channel if you plan to use multiple channels (e.g. a social networking app could have a channel for follow requests, one for direct messages received, one for likes to posts, etc.).
Here’s what the app-level notification settings look like:
and here’s what the channel-specific settings look like:
Here’s an example, where notifications
is the FlutterLocalNotificationsPlugin
object:
void showNotification(String title, String body) {
notifications.show(0, title, body, NotificationDetails(
AndroidNotificationDetails(
"my_app_channel_id_0",
"User-Visible Notification Channel Title",
"Useful, user-visible description of the notification channel"
),
IOSNotificationDetails()
));
}
This second part of the post is going to be about using the plugin in an app, more specifically changing the app we built in the previous post to also display notifications to the user. If you don’t want to read that but still want the code we are starting from, here’s the GitHub repo of the previous post’s app, whereas this is the one for the app we’ll build in this post.
First of all, add flutter_local_notifications
to the dependencies
in pubspec.yaml
:
and import it at the start of main.dart
. You’ll notice we are also importing the status codes for the WebSocket library, which we’ll need when we close the connection to the server in the dispose()
method of the State
of the AnnouncementPage
:
The AnnouncementPage
becomes a StatefulWidget
, we don’t use a StreamBuilder
, instead we wait for new data, change what’s displayed and also show a notifications. here’s the entire AnnouncementPage
and AnnouncementPageState
that we’ll analyze in more detail in the next section of the post:
One of the changes you need to keep in mind is that we now have a text
String
variable that we will update as we get data from the server, and that text
is what’s going to be displayed on the user’s screen.
The flutter_local_notifications
package has to be initialized. This can be done in multiple places. You could to do it in main
and have a global variable to be able to access the notifications plugin from anhywhere within the app without having to use InheritedWidget
s or the Provider
. In our case, though, we don’t really need that, as there really is only one widget that’s going to cause notifications to be displayed, which is our app’s AnnouncementPage
. What we need to do is define a StreamSubscription
that listens for changes in the stream received from the WebSocket
and, if there are any, both updates the AnnouncementPage
to show the new announcement (by calling initState
and setting the text
variable) and displays a notification.
All of this is done in the initState
method:
build
MethodThe build
method now simply needs to display the text
we already get using the StreamSubscription
, without requiring the use of a StreamBuilder
. If the connection hasn’t been established yet, which would mean text
is still null
, we display a CircularProgressIndicator
as we did when we used a StreamBuilder
and we had no data yet. Given that the nickname is now defined inside the AnnouncementPage
StatefulWidget
definition, we need to access it using the this.nickname
syntax in the onPressed
callback for the FloatingActionButton
that sends announcements.
dispose
MethodThe dispose()
method needs to close the WebSocket
, letting the server know it’s being closed and freeing resources on both ends of the connection, and cancel
s the StreamSubscription
because it’s not necessary anymore to listen for new announcements.
That’s it! The app now sends notifications when it gets new announcements (including when it’s the one generating them, but for this app this is intended)!
The entire main.dart
is going to be the following:
People have complained that I focused on Firebase too much in my book, so I’m trying to compensate here on my blog by focusing on more traditional backend implementations and ways to do things.
One caveat is that, for example, the only way to be able to send notifications to closed apps on both Android and iOS is to use Firebase Cloud Messaging, which can send notifications directly to Android devices (what used to be called Cloud to Device Messaging and then Google Cloud Messaging) and uses the Apple Push Notification Service to send notifications to iOS devices.
You can now subscribe to my newsletter to get email updates about new posts and, as always you can follow me on Twitter if you are active on there and want to be updated, and remember to check out my Flutter book in case you haven’t yet.