- Service: start a service to listen the notifications.
- Simple: it's simple to access notification's fields.
- Backgrounded: execute the dart code in the background and auto start the service after reboot.
- Interactive: the notification is interactive in flutter.
Open the pubspec.yaml
file located inside the app folder, and add flutter_notification_listener
: under dependencies
.
dependencies:
flutter_notification_listener: <latest_version>
Then you should install it,
- From the terminal: Run
flutter pub get
. - From Android Studio/IntelliJ: Click Packages get in the action ribbon at the top of
pubspec.yaml
. - From VS Code: Click Get Packages located in right side of the action ribbon at the top of
pubspec.yaml
.
1. Register the service in the manifest
The plugin uses an Android system service to track notifications. To allow this service to run on your application, the following code should be put inside the Android manifest, between the tags.
<service android:name="im.zoe.labs.flutter_notification_listener.NotificationsHandlerService"
android:label="Flutter Notifications Handler"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
And don't forget to add the permissions to the manifest,
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
2. Init the plugin and add listen handler
We have a default static event handler which send event with a channel. So if you can listen the event in the ui logic simply.
// define the handler for ui
void onData(NotificationEvent event) {
print(event.toString());
}
Future<void> initPlatformState() async {
NotificationsListener.initialize();
// register you event handler in the ui logic.
NotificationsListener.receivePort.listen((evt) => onData(evt));
}
3. Check permission and start the service
void startListening() async {
print("start listening");
var hasPermission = await NotificationsListener.hasPermission;
if (!hasPermission) {
print("no permission, so open settings");
NotificationsListener.openPermissionSettings();
return;
}
var isR = await NotificationsListener.isRunning;
if (!isR) {
await NotificationsListener.startService();
}
setState(() => started = true);
}
Please check the ./example/lib/main.dart for more detail.
It's every useful while you want to start listening notifications automatically after reboot.
Register a broadcast receiver in the AndroidManifest.xml
,
<receiver android:name="im.zoe.labs.flutter_notification_listener.RebootBroadcastReceiver"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Then the listening service will start automatically when the system fired the BOOT_COMPLETED
intent.
And don't forget to add the permissions to the manifest,
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- this pemission is for auto start service after reboot -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
You should know that the function
(evt) => onData(evt)
would not be called if the ui thread is not running.
callbackHandle
to handle the event which make sure events consumed.
That means the callbackHandle
static function is guaranteed, while the channel handle function is not. This is every useful when you should persist the events to the database.
We want to run some code in background without UI thread, like persist the notifications to database or storage.
-
Define your own callback to handle the incoming notifications.
static void _callback(NotificationEvent evt) { // persist data immediately db.save(evt) // send data to ui thread if necessary. // try to send the event to ui print("send evt to ui: $evt"); final SendPort send = IsolateNameServer.lookupPortByName("_listener_"); if (send == null) print("can't find the sender"); send?.send(evt); }
-
Register the handler when invoke the
initialize
.Future<void> initPlatformState() async { // register the static to handle the events NotificationsListener.initialize(callbackHandle: _callback); }
-
Listen events in the UI thread if necessary.
// define the handler for ui void onData(NotificationEvent event) { print(event.toString()); } Future<void> initPlatformState() async { // ... // register you event handler in the ui logic. NotificationsListener.receivePort.listen((evt) => onData(evt)); }
Before you start the listening service, you can offer some parameters.
await NotificationsListener.startService({
bool foreground = true, // use false will not promote to foreground and without a notification
String title = "Change the title",
String description = "Change the text",
});
We can tap the notification if it can be triggered in the flutter side.
For example, tap the notification automatically when the notification arrived.
// define the handler for ui
void onData(NotificationEvent event) {
print(event.toString());
// tap the notification automatically
// usually remove the notification
if (event.canTap) event.tap();
}
The notifications from some applications will setted the actions. We can interact with the notificaions in the flutter side.
For example, make the notification as readed automatically when the notification arrived.
// define the handler for ui
void onData(NotificationEvent event) {
print(event.toString());
events.actions.forEach(act => {
// semantic code is 2 means this is an ignore action
if (act.semantic == 2) {
act.tap();
}
})
}
Android provider a quick replying method in the notification. So we can use this to implement a reply logic in the flutter.
For example, reply to the conversation automatically when the notification arrived.
// define the handler for ui
void onData(NotificationEvent event) {
print(event.toString());
events.actions.forEach(act => {
// semantic is 1 means reply quick
if (act.semantic == 1) {
Map<String, dynamic> map = {};
act.inputs.forEach((e) {
print("set inputs: ${e.label}<${e.resultKey}>");
map[e.resultKey] = "Auto reply from flutter";
});
// send to the data
act.postInputs(map);
}
})
}
Fields of NotificationEvent
:
uniqueId
:String
, unique id of the notification which generated fromkey
.key
:String
, key of the status bar notification, required android sdk >= 20.packageName
:String
, package name of the application which notification posted by.uid
:int
, uid of the notification, required android sdk >= 29.channelId
:String
channel if of the notification, required android sdk >= 26.id
:int
, id of the notification.createAt
:DateTime
, created time of the notfication in the flutter side.timestamp
:int
, post time of the notfication.title
:title
, title of the notification.text
:String
, text of the notification.hasLargeIcon
:bool
, if this notification has a large icon.largeIcon
:Uint8List
, large icon of the notification which setted by setLargeIcon. To display as a image use the Image.memory widget.canTap
:bool
, if this notification has content pending intent.raw
:Map<String, dynamic>
, the original map of this notification, you can get all fields.
Other original fields in raw
which not assgin to the class:
subText
:String
, subText of the notification.summaryText
:String
, summaryText of the notification.textLines
:List<String>
, multi text lines of the notification.showWhen
:bool
, if show the time of the notification.
Methods for notification:
Future<bool> tap()
: tap the notification if it can be triggered, you should checkcanTap
first. Normally will clean up the notification.Future<dynamic> getFull()
: get the full notification object from android.
Fields of Action
:
id
:int
, the index of the action in the actions arraytitle
:String
, title of the actionsemantic
:int
, semantic type of the action, check below for detailsinputs
:ActionInput
, emote inputs list of the action
Action's semantic types:
SEMANTIC_ACTION_ARCHIVE = 5;
SEMANTIC_ACTION_CALL = 10;
SEMANTIC_ACTION_DELETE = 4;
SEMANTIC_ACTION_MARK_AS_READ = 2;
SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
SEMANTIC_ACTION_MUTE = 6;
SEMANTIC_ACTION_NONE = 0;
SEMANTIC_ACTION_REPLY = 1;
SEMANTIC_ACTION_THUMBS_DOWN = 9;
SEMANTIC_ACTION_THUMBS_UP = 8;
SEMANTIC_ACTION_UNMUTE = 7;
For more details, please see Notification.Action Constants.
Methods of Action
:
Future<bool> tap()
: tap the action of the notification. If action's semantic code is1
, it can't be tapped.Future<bool> postInputs(Map<String, dynamic> map)
: post inputs to the notification, useful for replying automaticly. Only works when semantic code is1
.
Fields of ActionInput
:
label
:String
, label for input.resultKey
:String
, result key for input. Must use correct to post data to inputs.
Fields of NotificationsListener
:
isRunning
:bool
, check if the listener service is running.hasPermission
:bool
, check if grant the permission to start the listener service.receivePort
:ReceivePort
, default receive port for listening events.
Static methods of NotificationsListener
:
Future<void> initialize()
: initialize the plugin, must be called at first.Future<void> registerEventHandle(EventCallbackFunc callback)
: register the event handler which will be called from android service, shoube be static function.Future<void> openPermissionSettings()
: open the system listen notifactoin permission setting page.Future<bool?> startService({...})
: start the listening service. arguments,foreground
:bool
, optional, promote the service to foreground.subTitle
:String
, optional, sub title of the service's notification.title
:String
, optional, title of the service's notification.description
:String
, optional, text contenet of the service's notification.showWhen
:bool
, optional
Future<bool?> stopService()
: stop the listening service.Future<void> promoteToForeground({...})
proomte the service to the foreground. Arguments are samestartService
.Future<void> demoteToBackground()
: demote the service to background.
- If the service is not foreground, service will start failed after reboot.
Did you find this plugin useful? Please consider to make a donation to help improve it!
Contributions are always welcome!