Awesome Notifications for Flutter - Year 2
Engage your users with custom local and push notifications on Flutter. Get real-time events and never miss a user interaction with Awesome Notifications.
Key Features:
- Create custom notifications: Use Awesome Notifications to easily create and customize local and push notifications for your Flutter app.
- Engage your users: Keep your users engaged with your app by sending notifications with images, sounds, emoticons, buttons, and many different layouts.
- Real-time events: Receive real-time events on the Flutter level code for notifications that are created, displayed, dismissed, or tapped by the user.
- Highly customizable: With a range of customizable options, including translations, you can tailor notifications to fit your specific needs.
- Scheduled notifications: Schedule notifications repeatedly or at specific times with second precision to keep your users up-to-date.
- Trusted performance: Receive notifications with confidence and trust in any application lifecycle.
- Easy to use: With an easy-to-use interface, you can start creating custom notifications in minutes.
Android notification examples:
iOS notification examples:
Notification Types Available
Here are some of the notification types available with Awesome Notifications:
- Basic notification
- Big picture notification
- Media notification
- Big text notification
- Inbox notification
- Messaging notification
- Messaging group notification
- Notifications with action buttons
- Grouped notifications
- Progress bar notifications (only for Android)
All notification types can be created locally or via remote push services, with all the features and customizations available.
๐ ATTENTION - PLUGIN UNDER DEVELOPMENT
Working Progress Percentages of Awesome Notifications Plugin
OBS: Please note that these progress percentages are estimates and are subject to change. We are continually working to improve the Awesome Notifications plugin and add support for new platforms.
๐ฐ Donate via Stripe or BuyMeACoffee
Help us improve and maintain our work with donations of any amount via Stripe or BuyMeACoffee. Your donation will mainly be used to purchase new devices and equipment, which we will use to test and ensure that our plugins work correctly on all platforms and their respective versions.
๐ฌ Discord Chat Server
Stay up to date with new updates and get community support by subscribing to our Discord chat server:
โ Next steps
- Include
Web support
- Include
Desktop support
- Include
Live Activities notifications
- Include
Time Sensitive notifications
- Include
Communication notifications
- Include full
Media Player notifications
- Implement test cases for native libraries to achieve +90% test coverage in each one
- Include support for other push notification services (Wonderpush, One Signal, IBM, AWS, Azure, etc)
- Replicate all Android layouts for iOS (almost accomplished)
- Custom layouts for notifications
We are constantly working to improve Awesome Notifications and provide support for new platforms and services. Stay tuned for more updates!
๐ ATTENTION: Third-Party Plugin Restrictions
Incompatibility Notice: The awesome_notifications
plugin is incompatible with flutter_local_notifications
or any other notification plugin. These plugins may conflict with each other when trying to acquire global notification resources.
Important: Do not use awesome_notifications
in conjunction with other third party notification plugins. If you have any questions or need further clarification, please visit our Discord Community for support and information.
๐ Migration Guides from Older Versions
Breaking Changes
From Version 0.8.X to 0.9.X:
-
Awesome Notifications Podfile Modification: Modifications to the
Podfile
file are now required for local notifications on iOS. This update ensures that your pod packages are configured correctly for both main and extended targets on iOS.Before:
post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end end
Now:
post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end ################ Awesome Notifications pod modification ################### awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks') require awesome_pod_file update_awesome_pod_build_settings(installer) ################ Awesome Notifications pod modification ################### end ################ Awesome Notifications pod modification ################### awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks') require awesome_pod_file update_awesome_main_target_settings('Runner', File.dirname(File.realpath(__FILE__)), flutter_root) ################ Awesome Notifications pod modification ###################
-
progress property changed to double: The property
progress
fromNotificationContent
was changed to double type to increase precision. -
Send port and Receive port with data restriction: The methods
sendPort.send()
andreceivePort!.listen()
now only accept serialized data, not objects. Convert your data to a map format and reconstruct it later from this format.Before:
/// At your initialization receivePort!.listen((receivedAction) { onActionReceivedMethodImpl(receivedAction); }); // (...) // Later in your action handler SendPort? sendPort = IsolateNameServer .lookupPortByName('notification_action_port'); if (sendPort != null) { sendPort.send(receivedAction); }
Now:
/// At your initialization receivePort!.listen((serializedData) { final receivedAction = ReceivedAction().fromMap(serializedData); onActionReceivedMethodImpl(receivedAction); }); // (...) // Later in your action handler SendPort? sendPort = IsolateNameServer .lookupPortByName('notification_action_port'); if (sendPort != null) { dynamic serializedData = receivedAction.toMap(); sendPort.send(serializedData); }
from version 0.6.X to 0.7.X:
-
Action Events Update: Now it's possible to receive action events without bringing the app to the foreground. Please refer to the action type's topic for more information on how to implement this.
-
Streams replaced by global static methods: All streams (createdStream, displayedStream, actionStream, and dismissedStream) have been replaced by global static methods. Replace your old stream methods with static and global ones, using static Future
-
Delayed notification events: All notification events are now delivered only after the first setListeners being called. Please make sure to update your code accordingly.
-
Renamed ButtonType class: The
ButtonType
class has been renamed toActionType
. Please update your code to use the new class name. -
Deprecated InputField action type: The action type
InputField
is now deprecated. You just need to set the propertyrequireInputText
to true to achieve the same result, but it now works combined with all other action types. -
Deprecated support for firebase_messaging plugin: The support for
firebase_messaging
plugin is now deprecated. You need to use the Awesome's FCM add-on plugin to achieve all Firebase Cloud Messaging features without violating the platform rules. This is the only way to fully integrate with Awesome Notifications, running all in native level.
๐ Table of Contents
- Awesome Notifications for Flutter - Year 2
- ๐ ATTENTION - PLUGIN UNDER DEVELOPMENT
- ๐ฐ Donate via Stripe or BuyMeACoffee
- ๐ฌ Discord Chat Server
- โ Next steps
- ๐ Table of Contents
- ๐ถ Main Philosophy of Awesome Notifications
- ๐ Getting Started
- ๐จ How to show Local Notifications
- ๐โบ Extra iOS Setup for Background Actions
- ๐ฑ Example Apps
- ๐ท Awesome Notification's Flowchart
- โก๏ธ Notification Events
- ๐ Notification Action Types
- ๐ฆ Notification's Category
- ๐ฎโโ๏ธ Requesting Permissions
- ๐ก Notification channels
- ๐ Scheduling a Notification
- ๐ Translation of Notification Content
- โฑ Chronometer and Timeout (Expiration)
- โ๏ธ Progress Bar Notifications (Only for Android)
- ๐ Emojis (Emoticons)
- ๐จ Notification Layout Types
- ๐ท Media Source Types
- โฌ๏ธ Notification Importance
- ๐ Wake Up Screen Notifications
- ๐ฅ Full Screen Notifications (only for Android)
- ๐ Notification Structures
- Common Known Issues
- Issue: Targeting S+ (version 31 and above) requires that an explicit value for android:exported be defined when intent filters are present
- Issue: Notification is not showing up or is showing up inconsistently.
- Issue: My schedules are only displayed immediately after I open my app
- Issue: DecoderBufferCallback not found / Uint8List not found
- Issue: Using bridging headers with module interfaces is unsupported
- Issue: Invalid notification content
- Issue: Undefined symbol: OBJC_CLASS$_FlutterStandardTypedData / OBJC_CLASS$_FlutterError / OBJC_CLASS$_FlutterMethodChannel
- Android Foreground Services (Optional)
๐ถ Main Philosophy of Awesome Notifications
At Awesome Notifications, we prioritize making notification implementation both straightforward and feature-rich. This approach allows developers to concentrate on content creation rather than the technical challenges posed by the diverse capabilities of different devices. Our philosophy is to make full use of all available notification features on a device while seamlessly omitting those that are not supported.
Key aspects of our philosophy include:
- Consistent Delivery: We ensure that all notifications sent while the app is inactive are recorded and delivered promptly after the app restarts and listener setups are complete.
- Device-Specific Adaptation: We automatically ignore device-specific features (e.g., LED lights) when they are not available, adapting notifications to each device's capabilities.
- Notification Channels: We strive to emulate Android's channel configuration behavior in iOS devices, providing a familiar and intuitive experience across platforms.
- App Badge Management: We strive to emulate iOS's approach to app badge manipulation as closely as possible on all other platforms.
By adhering to these principles, Awesome Notifications is committed to delivering a seamless and consistent notification experience across various devices, thereby enhancing user engagement with your application.
๐ Getting Started
Initial Configurations
To use the awesome_notifications
, follow these steps:
- Add the
awesome_notifications
plugin to your project'spubspec.yaml
file:
dependencies:
awesome_notifications_core: ^0.9.0 # <~ always ensure to use the latest version
awesome_notifications: any # <~ this version is managed by awesome_notifications_core package
After adding the dependency, run the following command to get the package:
flutter pub get
Now you need to modify some files in native libraries to meet to use awesome_notifications properly. Let's start with the Android configurations:
๐ค Configuring Android for Awesome Notifications:
1 - Is required the minimum android SDK to 21 (Android 5.0 Lollipop), Grade 7.3.0 or greater and Java compiled SDK Version to 34 (Android 14). You can change the minSdkVersion
to 21 and the compileSdkVersion
and targetSdkVersion
to 34, inside the file build.gradle
, located inside "android/app/" folder.
buildscript {
...
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
}
}
android {
compileSdkVersion 34
defaultConfig {
minSdkVersion 21
targetSdkVersion 34
...
}
...
}
2 - In the appโs AndroidManifest.xml
file (which can be found in the android/app/src/main directory of your Flutter project), add the following permissions:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application>
...
</application>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
</manifest>
3 - If you're using any <activity>
, <activity-alias>
, <service>
, or <receiver>
components with <intent-filter>
declared inside, add the attribute android:exported="true"
to make them accessible from outside the app's context.:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application>
...
<activity
android:name=".MainActivity"
...
android:exported="true">
...
</activity>
...
</application>
</manifest>
๐ Configuring iOS for Awesome Notifications:
To ensure that Awesome Notifications is correctly configured with your iOS app, you need to modify your Podfile
within the iOS folder. This involves adding specific blocks for "Awesome Notifications pod modifications" at the end of the post_install
section and immediately following it.
Original Podfile
Configuration:
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
Modified Podfile
with Awesome Notifications Configurations:
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings target
end
################ Awesome Notifications pod modification 1 ###################
awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks')
require awesome_pod_file
update_awesome_pod_build_settings(installer)
################ Awesome Notifications pod modification 1 ###################
end
################ Awesome Notifications pod modification 2 ###################
awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks')
require awesome_pod_file
update_awesome_main_target_settings('Runner', File.dirname(File.realpath(__FILE__)), flutter_root)
################ Awesome Notifications pod modification 2 ###################
Key Points:
- The first modification block updates the pod settings (
update_awesome_pod_build_settings
). - The second modification block updates the settings of the main project target 'Runner' (
update_awesome_main_target_settings
). - The 2 blocks are not equal. The first one calls
update_awesome_pod_build_settings
and the second one callsupdate_awesome_main_target_settings
Also, the modifications are clearly marked with ################ Awesome Notifications pod modification ################
comments for easy identification. These additions ensure that Awesome Notifications are properly integrated and configured within your iOS project's Podfile.
๐จ How to show Local Notifications
- Add awesome_notifications as a dependency in your
pubspec.yaml
file.
awesome_notifications: any # Any attribute updates automatically your source to the last version
- import the plugin package to your dart code
import 'package:awesome_notifications/awesome_notifications.dart';
- Initialize the plugin on main.dart, before MaterialApp widget (preferentially inside
main()
method), with at least one native icon and one channel
AwesomeNotifications().initialize(
// set the icon to null if you want to use the default app icon
'resource://drawable/res_app_icon',
[
NotificationChannel(
channelGroupKey: 'basic_channel_group',
channelKey: 'basic_channel',
channelName: 'Basic notifications',
channelDescription: 'Notification channel for basic tests',
defaultColor: Color(0xFF9D50DD),
ledColor: Colors.white)
],
// Channel groups are only visual and are not required
channelGroups: [
NotificationChannelGroup(
channelGroupKey: 'basic_channel_group',
channelGroupName: 'Basic group')
],
debug: true
);
- Inside the MaterialApp widget, create your named routes and set your global navigator key. Also, inside initState, initialize your static listeners methods to capture notification's actions. OBS 1: With the navigator key, you can redirect pages and get context even inside static classes. OBS 2: Only after setListeners being called, the notification events starts to be delivered.
class MyApp extends StatefulWidget {
static final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
static const String name = 'Awesome Notifications - Example App';
static const Color mainColor = Colors.deepPurple;
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
// Only after at least the action method is set, the notification events are delivered
AwesomeNotifications().setListeners(
onActionReceivedMethod: NotificationController.onActionReceivedMethod,
onNotificationCreatedMethod: NotificationController.onNotificationCreatedMethod,
onNotificationDisplayedMethod: NotificationController.onNotificationDisplayedMethod,
onDismissActionReceivedMethod: NotificationController.onDismissActionReceivedMethod
);
super.initState();
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
// The navigator key is necessary to allow to navigate through static methods
navigatorKey: MyApp.navigatorKey,
title: MyApp.name,
color: MyApp.mainColor,
initialRoute: '/',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) =>
MyHomePage(title: MyApp.name)
);
case '/notification-page':
return MaterialPageRoute(builder: (context) {
final ReceivedAction receivedAction = settings
.arguments as ReceivedAction;
return MyNotificationPage(receivedAction: receivedAction);
});
default:
assert(false, 'Page ${settings.name} not found');
return null;
}
},
theme: ThemeData(
primarySwatch: Colors.deepPurple
),
);
}
}
OBS: Note that the example below is not a valid static or global method. You can retrieve the current context from the NavigatorKey instance, declared on MaterialApp widget, at any time.
AwesomeNotifications().setListeners(
onActionReceivedMethod: (ReceivedAction receivedAction){
NotificationController.onActionReceivedMethod(context, receivedAction);
},
onNotificationCreatedMethod: (ReceivedNotification receivedNotification){
NotificationController.onNotificationCreatedMethod(context, receivedNotification);
},
onNotificationDisplayedMethod: (ReceivedNotification receivedNotification){
NotificationController.onNotificationDisplayedMethod(context, receivedNotification);
},
onDismissActionReceivedMethod: (ReceivedAction receivedAction){
NotificationController.onDismissActionReceivedMethod(context, receivedAction);
},
);
- Create in any place or class, the static methods to capture the respective notification events.
OBS: You need to use
@pragma("vm:entry-point")
in each static method to identify to the Flutter engine that the dart address will be called from native and should be preserved.
class NotificationController {
/// Use this method to detect when a new notification or a schedule is created
@pragma("vm:entry-point")
static Future <void> onNotificationCreatedMethod(ReceivedNotification receivedNotification) async {
// Your code goes here
}
/// Use this method to detect every time that a new notification is displayed
@pragma("vm:entry-point")
static Future <void> onNotificationDisplayedMethod(ReceivedNotification receivedNotification) async {
// Your code goes here
}
/// Use this method to detect if the user dismissed a notification
@pragma("vm:entry-point")
static Future <void> onDismissActionReceivedMethod(ReceivedAction receivedAction) async {
// Your code goes here
}
/// Use this method to detect when the user taps on a notification or action button
@pragma("vm:entry-point")
static Future <void> onActionReceivedMethod(ReceivedAction receivedAction) async {
// Your code goes here
// Navigate into pages, avoiding to open the notification details page over another details page already opened
MyApp.navigatorKey.currentState?.pushNamedAndRemoveUntil('/notification-page',
(route) => (route.settings.name != '/notification-page') || route.isFirst,
arguments: receivedAction);
}
}
- Request the user authorization to send local and push notifications (Remember to show a dialog alert to the user before call this request)
AwesomeNotifications().isNotificationAllowed().then((isAllowed) {
if (!isAllowed) {
// This is just a basic example. For real apps, you must show some
// friendly dialog box before call the request method.
// This is very important to not harm the user experience
AwesomeNotifications().requestPermissionToSendNotifications();
}
});
- In any place of your app, create a new notification with:
AwesomeNotifications().createNotification(
content: NotificationContent(
id: 10,
channelKey: 'basic_channel',
actionType: ActionType.Default
title: 'Hello World!',
body: 'This is my first notification!',
)
);
This will create a new notification with ID 10
, using the previously defined notification channel basic_channel
and the default action type that brings the app to foreground. The notification will have a title of "Hello World!" and a body of "This is my first notification!".
๐๐๐ THATS IT! CONGRATZ MY FRIEND!!! ๐๐๐
๐ Getting started - Important notes
1 . You MUST initialize all Awesome Notifications plugins, even if your app does not have permissions to send notifications.
2 . In case you need to capture the user notification action before calling the method setListeners
, you can call the method getInitialNotificationAction
at any moment.
In case your app was started by an user notification action, getInitialNotificationAction
will return the respective ActionReceived
object. Otherwise will return null.
OBS: getInitialNotificationAction
method does not affect the results from onActionReceivedMethod
, except if you set removeFromActionEvents
to true
.
void main() async {
ReceivedAction? receivedAction = await AwesomeNotifications().getInitialNotificationAction(
removeFromActionEvents: false
);
if(receivedAction?.channelKey == 'call_channel') setInitialPageToCallPage();
else setInitialPageToHomePage();
}
3 . In case you need to redirect the user after a silentAction
or silentBackgroundAction
event, you may face the situation where you are running inside a dart Isolate with no valid BuildContext
to redirect the user. For such cases, you can use SendPort
and ReceivePort
to switch execution between isolates.
First, create a ReceivePort
inside your initialization process (which only occurs in the main isolate). Then, inside your onActionReceivedMethod
, check if you are running inside the main isolate first. If not, use a SendPort
to send the execution to the listening ReceivePort
. Inside the ReceivePort
listener, you can then call the appropriate method to handle the background action.
Here is an example:
In the initialization of your notification_controller.dart
:
// Create a receive port
ReceivePort port = ReceivePort();
// Register the receive port with a unique name
IsolateNameServer.registerPortWithName(
port,
'notification_actions',
);
// Listen for messages on the receive port
port.listen((var serializedData) async {
print('Action running on main isolate');
final receivedAction = ReceivedAction().fromMap(serializedData);
_handleActionReceived(receivedAction);
});
// Set the initialization flag
_initialized = true;
In your onActionReceivedMethod
method:
static Future<void> onActionReceivedMethod(ReceivedAction received) async {
print('New action received: ${received.toMap()}');
// If the controller was not initialized or the function is not running in the main isolate,
// try to retrieve the ReceivePort at main isolate and them send the execution to it
if (!_initialized) {
SendPort? uiSendPort = IsolateNameServer.lookupPortByName('notification_actions');
if (uiSendPort != null) {
print('Background action running on parallel isolate without valid context. Redirecting execution');
uiSendPort.send(received.toMap());
return;
}
}
print('Action running on background isolate');
await _handleActionReceived(received);
}
static Future<void> _handleActionReceived(ReceivedAction received) async {
// Here you handle your notification actions
// Navigate into pages, avoiding to open the notification details page twice
// In case youre using some state management, such as GetX or get_route, use them to get the valid context instead
// of using the Flutter's navigator key
MyApp.navigatorKey.currentState?.pushNamedAndRemoveUntil('/notification-page',
(route) => (route.settings.name != '/notification-page') || route.isFirst,
arguments: receivedAction);
}
- On Android, if you press the back button until leaves the app and then reopen it using the "Recent apps list"
THE LAST APP INITIALIZATION WILL BE REPEATED
. So, in the case where the app was started up by a notification, in this exclusive case the notification action will be repeated. If this is not desirable behavior for your app, you will need to handle this case specifically in your app's logic.
๐โบ Extra iOS Setup for Background Actions
On iOS, to use any plugin inside background actions, you will need to manually register each plugin you want. Otherwise, you will face the MissingPluginException
exception.
To avoid this, you need to add the following lines to the AppDelegate.swift
file in your iOS project folder:
import Flutter
import awesome_notifications
import shared_preferences_ios
//import all_other_plugins_that_i_need
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// This function registers the desired plugins to be used within a notification background action
SwiftAwesomeNotificationsPlugin.setPluginRegistrantCallback { registry in
SwiftAwesomeNotificationsPlugin.register(
with: registry.registrar(forPlugin: "io.flutter.plugins.awesomenotifications.AwesomeNotificationsPlugin")!)
FLTSharedPreferencesPlugin.register(
with: registry.registrar(forPlugin: "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")!)
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
You can also check the GeneratedPluginRegistrant.m
file to see the correct plugin names to use. (Note that the plugin names may change over time)
๐ฑ Example Apps
With the examples bellow, you can check all the features and how to use the Awesome Notifications in pratice. The Simple Example app contains the basic structure to use Awesome Notifications, and the Complete Example App contains all Awesome Notification features to test.
To run and debug the Simple Example App, follow the steps bellow:
- Create a new Flutter project with at least Android or iOS
- Copy the example code at https://pub.dev/packages/awesome_notifications/example
- Paste the content inside the
main.dart
file - Debug the application with a real device or emulator
To run and debug the Complete Example App, follow the steps bellow:
- Install GitHub software in your local machine. I strongly recommend to use GitHub Desktop.
- Go to one of our GitHub repositories
- Clone the project to your local machine
- Open the project with Android Studio or any other IDE
- Sync the project dependencies running
flutter pub get
- On iOS, run
pod install
inside the folder example/ios/ to sync the native dependencies - Debug the application with a real device or emulator
๐ท Awesome Notification's Flowchart
Notifications are received by local code or Push service using native code, so the messages will appears immediately or at schedule time, independent of your application is running or not.
โก๏ธ Notification Events
The notification events are only delivered after setListeners
method being called, and they are not always delivered at same time as they happen.
The awesome notifications event methods available to track your notifications are:
- onNotificationCreatedMethod (optional): Fires when a notification is created
- onNotificationDisplayedMethod (optional): Fires when a notification is displayed on system status bar
- onActionReceivedMethod (required): Fires when a notification is tapped by the user
- onDismissedActionReceivedMethod (optional): Fires when a notification is dismissed by the user (sometimes the OS denies the deliver)
... and these are the delivery conditions:
Platform | App in Foreground | App in Background | App Terminated (Force Quit) |
---|---|---|---|
Android | Fires all events immediately after occurs | Fires all events immediately after occurs | Store events to be fired when app is on Foreground or Background |
iOS | Fires all events immediately after occurs | Store events to be fired when app is on Foreground | Store events to be fired when app is on Foreground |
Exception: onActionReceivedMethod fires all events immediately after occurs in any application life cycle, for all Platforms.
๐ Notification Action Types
There are several types of notification actions that you can use in Awesome Notifications:
- Default: This is the default action type. It forces the app to go to the foreground when the user taps the notification.
- SilentAction: This type of action does not force the app to go to the foreground, but it runs on the main thread and can accept visual elements. It can be interrupted if the main app is terminated.
- SilentBackgroundAction: This type of action does not force the app to the foreground and runs in the background. It does not accept any visual element and the execution is done on an exclusive Dart isolate.
- KeepOnTop: This type of action fires the respective action without closing the notification status bar and does not bring the app to the foreground.
- DisabledAction: When the user taps this type of action, the notification simply closes itself on the tray without firing any action event.
- DismissAction: This type of action behaves the same way as a user dismissing action, but it dismisses the respective notification and fires the onDismissActionReceivedMethod. It ignores the autoDismissible property.
- InputField: (Deprecated) When the user taps this type of action, it opens a dialog box that allows them to send a text response. Now you can use the requireInputText property instead.
Remember that for silent types, it is necessary to use the await keyword to prevent the isolates from shutting down before all the work is done. Consider using these different types of actions to customize the behavior of your notifications to suit your needs.
๐ฆ Notification's Category
The notification category is a group of predefined categories that best describe the nature of the notification and may be used by some systems to rank, delay or filter the notifications.
It's highly recommended to always correctly categorize your notifications.
- Alarm: Alarm or timer.
- Call: incoming call (voice or video) or similar synchronous communication request
- Email: asynchronous bulk message (email).
- Error: error in background operation or authentication status.
- Event: calendar event.
- LocalSharing: temporarily sharing location.
- Message: incoming direct message (SMS, instant message, etc.).
- MissedCall: incoming call (voice or video) or similar synchronous communication request
- Navigation: map turn-by-turn navigation.
- Progress: progress of a long-running background operation.
- Promo: promotion or advertisement.
- Recommendation: a specific, timely recommendation for a single thing. For example, a news app might want to recommend a news story it believes the user will want to read next.
- Reminder: user-scheduled reminder.
- Service: indication of running background service.
- Social: social network or sharing update.
- Status: ongoing information about device or contextual status.
- StopWatch: running stopwatch.
- Transport: media transport control for playback.
- Workout: tracking a user's workout.
๐ฎโโ๏ธ Requesting Permissions
Permissions give transparency to the user about what you intend to do with your app while it's in use. To show any notification on a device, you must obtain the user's consent. Keep in mind that this consent can be revoked at any time, on any platform. On Android 12 and below, the basic permissions are always granted to any newly installed app. But for iOS and Android 13 and beyond, even the basic permission must be requested from the user.
Disclaimer: On Android, revoke certain permissions, including notification permissions, may cause the system to automatically restart the app to ensure the new permission setting is respected. Handle this scenario in your code by saving any necessary state before requesting or changing permissions, and restoring that state when the app is restarted. Inform your users about this behavior to avoid confusion and ensure a smoother user experience.
Permissions can be defined in three types:
- Normal permissions: These permissions are not considered dangerous and do not require explicit user consent to be enabled.
- Execution permissions: These permissions are considered more sensitive to the user, and you must obtain their explicit consent to use them.
- Special/Dangerous permissions: These permissions can harm the user experience or their privacy, and you must obtain their explicit consent. Depending on the platform, you may need permission from the manufacturer to use them.
As a good practice, always check if the permissions you desire are granted before creating any new notification, regardless of the platform. To check if a permission requires explicit user consent, call the method shouldShowRationaleToRequest
. The list of permissions that require a rationale to the user can differ between platforms and OS versions. If your app does not require a permission to execute what you need, consider not requesting it, respecting the user's will.
Notification's Permissions:
-
Alert: Alerts are notifications with high priority that pops up on the user screen. Notifications with normal priority only shows the icon on status bar.
-
Sound: Sound allows for the ability to play sounds for new displayed notifications. The notification sounds are limited to a few seconds and if you plan to play a sound for more time, you must consider to play a background sound to do it simultaneously with the notification.
-
Badge: Badge is the ability to display a badge alert over the app icon to alert the user about updates. The badges can be displayed on numbers or small dots, depending of platform or what the user defined in the device settings. Both Android and iOS can show numbers on badge, depending of its version and distribution.
-
Light: The ability to display colorful small lights, blanking on the device while the screen is off to alert the user about updates. Only a few Android devices have this feature.
-
Vibration: The ability to vibrate the device to alert the user about updates.
-
FullScreenIntent: The ability to show the notifications on pop up even if the user is using another app.
-
PreciseAlarms: Precise alarms allows the scheduled notifications to be displayed at the expected time. This permission can be revoke by special device modes, such as battery save mode, etc. Some manufactures can disable this feature if they decide that your app is consumpting many computational resources and decressing the baterry life (and without changing the permission status for your app). So, you must take in consideration that some schedules can be delayed or even not being displayed, depending of what platform are you running. You can increase the chances to display the notification at correct time, enable this permission and setting the correct notification category, but you never gonna have 100% sure about it.
-
CriticalAlert: Critical alerts is a special permission that allows to play sounds and vibrate for new notifications displayed, even if the device is in Do Not Disturb / Silent mode. For iOS, you must request Apple a authorization to your app use it.
-
OverrideDnD: Override DnD allows the notification to decrease the Do Not Disturb / Silent mode level enable to display critical alerts for Alarm and Call notifications. For Android, you must require the user consent to use it. For iOS, this permission is always enabled with CriticalAlert.
-
Provisional: (Only has effect on iOS) The ability to display notifications temporarily without the user consent.
-
Car: The ability to display notifications while the device is in car mode.
OBS: If none permission is requested through requestPermissionToSendNotifications
method, the standard permissions requested are Alert, Badge, Sound, Vibrate and Light.
Notification's Permission Level
A permission can be segregated in 3 different levels:
- Device level: The permissions set at the global device configuration are applicable at any app installed on device, such as disable/enable all notifications, battery save mode / low power mode and silent / do not disturb mode.
- Application level: The permissions set at the global app configurations are applicable to any notification in any channel.
- Channel level: The permissions set on the channel has effect only for notifications displayed through that specific channel.
Full example on how to request permissions
Below is a complete example of how to check if the desired permission is enabled and how to request it by showing a dialog with a rationale if necessary (this example is taken from our sample app):
static Future<List<NotificationPermission>> requestUserPermissions(
BuildContext context,{
// if you only intends to request the permissions until app level, set the channelKey value to null
required String? channelKey,
required List<NotificationPermission> permissionList}
) async {
// Check if the basic permission was granted by the user
if(!await requestBasicPermissionToSendNotifications(context))
return [];
// Check which of the permissions you need are allowed at this time
List<NotificationPermission> permissionsAllowed = await AwesomeNotifications().checkPermissionList(
channelKey: channelKey,
permissions: permissionList
);
// If all permissions are allowed, there is nothing to do
if(permissionsAllowed.length == permissionList.length)
return permissionsAllowed;
// Refresh the permission list with only the disallowed permissions
List<NotificationPermission> permissionsNeeded =
permissionList.toSet().difference(permissionsAllowed.toSet()).toList();
// Check if some of the permissions needed request user's intervention to be enabled
List<NotificationPermission> lockedPermissions = await AwesomeNotifications().shouldShowRationaleToRequest(
channelKey: channelKey,
permissions: permissionsNeeded
);
// If there is no permissions depending on user's intervention, so request it directly
if(lockedPermissions.isEmpty){
// Request the permission through native resources.
await AwesomeNotifications().requestPermissionToSendNotifications(
channelKey: channelKey,
permissions: permissionsNeeded
);
// After the user come back, check if the permissions has successfully enabled
permissionsAllowed = await AwesomeNotifications().checkPermissionList(
channelKey: channelKey,
permissions: permissionsNeeded
);
}
else {
// If you need to show a rationale to educate the user to conceived the permission, show it
await showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: Color(0xfffbfbfb),
title: Text('Awesome Notifications needs your permission',
textAlign: TextAlign.center,
maxLines: 2,
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w600),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/images/animated-clock.gif',
height: MediaQuery.of(context).size.height * 0.3,
fit: BoxFit.fitWidth,
),
Text(
'To proceed, you need to enable the permissions above'+
(channelKey?.isEmpty ?? true ? '' : ' on channel $channelKey')+':',
maxLines: 2,
textAlign: TextAlign.center,
),
SizedBox(height: 5),
Text(
lockedPermissions.join(', ').replaceAll('NotificationPermission.', ''),
maxLines: 2,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
],
),
actions: [
TextButton(
onPressed: (){ Navigator.pop(context); },
child: Text(
'Deny',
style: TextStyle(color: Colors.red, fontSize: 18),
)
),
TextButton(
onPressed: () async {
// Request the permission through native resources. Only one page redirection is done at this point.
await AwesomeNotifications().requestPermissionToSendNotifications(
channelKey: channelKey,
permissions: lockedPermissions
);
// After the user come back, check if the permissions has successfully enabled
permissionsAllowed = await AwesomeNotifications().checkPermissionList(
channelKey: channelKey,
permissions: lockedPermissions
);
Navigator.pop(context);
},
child: Text(
'Allow',
style: TextStyle(color: Colors.deepPurple, fontSize: 18, fontWeight: FontWeight.bold),
),
),
],
)
);
}
// Return the updated list of allowed permissions
return permissionsAllowed;
}
๐ก Notification channels
Notification channels are a way to group notifications that share common characteristics, such as the channel name, description, sound, vibration, LED light, and importance level. You can create and delete notification channels at any time in your app. However, at least one notification channel must exist during the initialization of the Awesome Notifications plugin. If you create a notification using an invalid channel key, the notification will be discarded.
In Android 8 (SDK 26) and later versions, you cannot update notification channels after they are created, except for the name and description attributes. However, for exceptional cases where you need to change your channels, you can set the forceUpdate
property to true in the setChannel
method. This option will delete the original channel and recreate it with a different native channel key. But use it only in cases of extreme need because this method deviates from the standard defined by the Android team. Note that this operation has the negative effect of automatically closing all active notifications on that channel.
For iOS, there is no native notification channel concept. However, Awesome Notifications will handle your notification channels in the same way as Android, so you only need to write your code once and it will work on both platforms.
You can also organize your notification channels visually in your Android app by using NotificationChannelGroup
in the AwesomeNotifications().initialize
method and the channelGroupKey
property in the respective channels. You can update the channel group name at any time, but a channel can only be defined in a group when it is created.
The main methods to manipulate notification channels are:
AwesomeNotifications().setChannel
: Creates or updates a notification channel.AwesomeNotifications().removeChannel
: Removes a notification channel, closing all current notifications on that channel. You can use the following attributes to configure your notification channels:
Notification Channel Attributes
Attribute | Required | Description | Type | Updatable | Default Value |
---|---|---|---|---|---|
channelKey |
Yes | A string key that identifies a channel where notifications are sent. | String | No | basic_channel |
channelName |
Yes | The name of the channel, which is visible to users on Android. | String | Yes | None |
channelDescription |
Yes | A brief description of the channel, which is visible to users on Android. | String | Yes | None |
channelShowBadge |
No | Whether the notification should automatically increment the app icon badge counter. | Boolean | Yes | false |
channelGroupKey |
No | A string key to group all channels in same topic at android's configuration page | String | No | None |
importance |
No | The importance level of the notification. | NotificationImportance | No | Normal |
playSound |
No | Whether the notification should play a sound. | Boolean | No | true |
soundSource |
No | The path of a custom sound file to be played with the notification. | String | No | None |
defaultRingtoneType |
No | The type of default sound to be played with the notification (only for Android). | DefaultRingtoneType | Yes | Notification |
enableVibration |
No | Whether the device should vibrate when the notification is received. | Boolean | No | true |
enableLights |
No | Whether the device should display a blinking LED when the notification is received. | Boolean | No | true |
ledColor |
No | The color of the LED to display when the notification is received. | Color | No | Colors.white |
ledOnMs |
No | The duration in milliseconds that the LED should remain on when displaying the notification. | Integer | No | None |
ledOffMs |
No | The duration in milliseconds that the LED should remain off when displaying the notification. | Integer | No | None |
groupKey |
No | The string key used to group notifications together in Android status bar. | String | No | None |
groupSort |
No | The order in which notifications within a group should be sorted. | GroupSort | No | Desc |
groupAlertBehavior |
No | The alert behavior to use for notifications within a group. | GroupAlertBehavior | No | All |
defaultPrivacy |
No | The level of privacy to apply to the notification when the device is locked. | NotificationPrivacy | No | Private |
icon |
No | The name of the notification icon to display in the status bar. | String | No | None |
defaultColor |
No | The color to use for the notification on Android. | Color | No | Color.black |
locked |
No | Whether the notification should be prevented from being dismissed by the user. | Boolean | No | false |
onlyAlertOnce |
No | Whether the notification should only alert the user once. | Boolean | No | false |
๐ Notification Channel's Important Notes:
1 - Notification channels cannot be modified after being created on devices running Android 8 (SDK 26) or later, unless the app is reinstalled or installed for the first time after the changes.
2 - In exceptional cases where modification is necessary, you can set the forceUpdate
property to true in the setChannel
method to delete the original channel and recreate it with a different native channel key. However, this method should only be used when absolutely necessary as it deviates from the standard defined by the Android team..
3 - Keep in mind that using forceUpdate
will also close all active notifications on the channel.
๐ Scheduling a Notification
Notifications can be scheduled either from a UTC or local time zone, and can be configured with a time interval or by setting a calendar filter. Schedules can also be created remotely using silent push notifications.
Note for iOS users: It is not possible to define the exact displayedDate
for a notification on iOS, as it is not possible due the impossibility to execute anything at same time as the scheduled time when it arrives in the user's status bar.
To schedule a notification, instantiate one of the classes below in the schedule
property of the notification:
NotificationCalendar
: Creates a notification that is scheduled to be displayed when the set date components match the current date. If a time component is set tonull
, then any value is considered valid to produce the next valid date. Only one value is allowed for each component.NotificationInterval
: Creates a notification that is scheduled to be displayed at each interval time, starting from the next valid interval.NotificationAndroidCrontab
: Creates a notification that is scheduled to be displayed based on a list of precise dates or a crontab rule, with seconds precision. To learn more about how to create a valid crontab rule, check out this article.
All of these classes can be configured with the following properties:
timeZone
: describes the time zone on which the schedule is based (valid examples include "America/Sao_Paulo", "America/Los_Angeles", "GMT+01:00", "Europe/London", "UTC").allowWhileIdle
: determines whether the notification will be sent even when the device is in a critical situation, such as low battery.repeats
: determines whether the schedule should repeat after the notification is displayed. If there are no more valid dates compatible with the schedule rules, the notification is automatically canceled.
Please note the following about time zones:
- Dates with UTC time zones are triggered at the same time in all parts of the planet and are not affected by daylight rules.
- Dates with local time zones, defined such as "GMT-07:00", are not affected by daylight rules.
- Dates with local time zones, defined such as "Europe/Lisbon", are affected by daylight rules, especially when scheduled based on a calendar filter.
Here are some practical examples of how to create a scheduled notification:
String localTimeZone = await AwesomeNotifications().getLocalTimeZoneIdentifier();
String utcTimeZone = await AwesomeNotifications().getLocalTimeZoneIdentifier();
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'scheduled',
title: 'Notification every single minute',
body:
'This notification was scheduled to repeat every minute.',
notificationLayout: NotificationLayout.BigPicture,
bigPicture: 'asset://assets/images/melted-clock.png'),
schedule: NotificationInterval(interval: 60, timeZone: localTimeZone, repeats: true));
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'scheduled',
title: 'Wait 5 seconds to show',
body: 'Now it is 5 seconds later.',
wakeUpScreen: true,
category: NotificationCategory.Alarm,
),
schedule: NotificationInterval(
interval: 5,
timeZone: localTimeZone,
preciseAlarm: true,
timezone: await AwesomeNotifications().getLocalTimeZoneIdentifier()
);
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'scheduled',
title: 'Notification at exactly every single minute',
body: 'This notification was schedule to repeat at every single minute at clock.',
notificationLayout: NotificationLayout.BigPicture,
bigPicture: 'asset://assets/images/melted-clock.png'),
schedule: NotificationCalendar(second: 0, timeZone: localTimeZone, repeats: true));
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'scheduled',
title: 'Just in time!',
body: 'This notification was scheduled to shows at ' +
(Utils.DateUtils.parseDateToString(scheduleTime.toLocal()) ?? '?') +
' $timeZoneIdentifier (' +
(Utils.DateUtils.parseDateToString(scheduleTime.toUtc()) ?? '?') +
' utc)',
wakeUpScreen: true,
category: NotificationCategory.Reminder,
notificationLayout: NotificationLayout.BigPicture,
bigPicture: 'asset://assets/images/delivery.jpeg',
payload: {'uuid': 'uuid-test'},
autoDismissible: false,
),
schedule: NotificationCalendar.fromDate(date: scheduleTime));
โฐ Schedule Precision
It's important to keep in mind that schedules can be ignored or delayed, especially for repeating schedules, due to system algorithms designed to save battery life and prevent abuse of resources. While this behavior is recommended to protect the app and the manufacturer's image, it's crucial to consider this fact in your business logic.
However, for cases where precise schedule execution is a must, there are some features you can use to ensure the notification is sent at the correct time:
- Set the notification's category to a critical one, such as Alarm, Reminder, or Call.
- Set the
preciseAlarm
property to true. This feature allows the system to schedule notifications to be sent at an exact time, even if the device is in low-power mode. For Android versions greater than or equal to 12, you need to explicitly request user consent to enable this feature. You can request the permission withrequestPermissionToSendNotifications
or take the user to the permission page by callingshowAlarmPage
. - Set the
criticalAlerts
channel property and notification content property to true. This feature allows you to show notifications and play sounds even when the device is on silent or Do Not Disturb mode. Due to its sensitivity, this feature requires special authorization from Apple on iOS and explicit user consent on Android versions greater than or equal to 11. On iOS, you must submit a request for authorization to Apple to enable it, as described in this post.
To enable precise alarms, you need to add the SCHEDULE_EXACT_ALARM
permission to your app's AndroidManifest.xml
file, located in the Android/app/src/main/ folder:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example">
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<application>
...
</application>
</manifest>
For Android 14 or greater, the SCHEDULE_EXACT_ALARM permission is denied by default, and you must request it from the users using requestPermissionToSendNotifications
.
To enable critical alerts, you need to add the ACCESS_NOTIFICATION_POLICY
permission to your app's AndroidManifest.xml
file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example">
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
<application>
...
</application>
</manifest>
In summary, if you need to ensure precise execution of scheduled notifications, make sure to use the appropriate categories and properties for your notifications, and enable the necessary permissions in your app's manifest file.
Additionally, you can ask your users to whitelist your app from any battery optimization feature that the device may have. This can be done by adding your app to the "unmonitored apps" or "battery optimization exceptions" list, depending on the device.
You can also try to use the flutter_background_service package to help schedule background tasks. This package allows you to schedule tasks that will run even when the app is not open, and it has some built-in features to help handle battery optimization.
To know more about it, please visit flutter_background_service_fetch documentation and Optimizing for Doze and App Standby for Android devices.
๐ Schedule Notification's Important Notes:
- Schedules may be delayed or denied if the device/application is in battery saver mode or locked to perform background tasks. Educate your users on why it's important to avoid these modes and the potential consequences of using them. Also, some battery saving modes may differ between manufacturers, such as Samsung and Xiaomi, which automatically enable battery saving mode for newly installed apps.
- On iOS, you can only schedule up to 64 notifications per app. On Android, you can schedule up to 500 notifications per app.
- If you're running your app in debug mode, all schedules may be erased by the Android OS when you close the app. This ensures consistent behavior when testing in debug mode. To test schedule notifications on Android when the app is not running, make sure to open the app without debugging.
- If your app doesn't require precise scheduling of notifications, avoid requesting exact notifications to conserve battery life.
- Categorize your notifications correctly to avoid scheduling delays.
- Note that critical alerts are still under development and should not be used in production mode.
Deprecated Schedule Class for Cron Rules (Versions Prior to 0.0.6)
Before version 0.0.6, Awesome Notifications included the Schedule class, which allowed users to schedule notifications based on cron tab rules. However, due to limitations with how background tasks and notification schedules work on iOS, it was not possible to fully support cron-based schedules on iOS devices while the app is in the background or terminated.
As a result, the NotificationAndroidCrontab
class was introduced as an alternative for Android users to create complex schedules based on cron tab rules. Unfortunately, Apple has not yet resolved the limitations with cron-based schedules on iOS, and there are no plans to support the deprecated Schedule class in future versions of Awesome Notifications.
A support ticket was opened for Apple in order to resolve this issue, but they don't even care about. For more information and updates on this issue, you can follow the progress of the support ticket here.
๐ Translation of Notification Content
The new NotificationLocalization class allows you to create a set of localized strings for a notification, including the title, body, summary, large icon, big picture, and button labels. This feature makes it easy to provide localized content for your users, which is essential for global applications.
To set the desired localization for notifications, use the setLocalization method. This method takes a required languageCode parameter, which is an optional, case-insensitive string that represents the language code for the desired localization. For example, you can set the language code to "en" for English, "pt-br" for Brazilian Portuguese, "es" for Spanish, and so on. If the localization was never set or redefined, the default localization will be loaded from the device system.
await AwesomeNotifications().setLocalization(languageCode: 'pt-br');
To get the current localization code used by the plugin for notification content, use the getLocalization method. This method returns a string representing the current localization code, which is a two-letter language code or a language code combined with a region code. If no localization has been set, this method will return the system's default language code.
String currentLanguageCode = await AwesomeNotifications().getLocalization();
Here's an example of how to use the localizations parameter to translate notification content into several languages:
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'basic_channel',
title: 'This title is written in english',
body: 'Now it is really easy to translate a notification content, '
'including images and buttons!',
summary: 'Awesome Notifications Translations',
notificationLayout: NotificationLayout.BigPicture,
bigPicture: 'asset://assets/images/awn-rocks-en.jpg',
largeIcon: 'asset://assets/images/american.jpg',
payload: {'uuid': 'user-profile-uuid'}),
actionButtons: [
NotificationActionButton(
key: 'AGREED1', label: 'I agree', autoDismissible: true),
NotificationActionButton(
key: 'AGREED2', label: 'I agree too', autoDismissible: true),
],
localizations: {
'pt-br' : NotificationLocalization(
title: 'Este tรญtulo estรก escrito em portuguรชs do Brasil!',
body: 'Agora รฉ muito fรกcil traduzir o conteรบdo das notificaรงรตes, '
'incluindo imagens e botรตes!',
summary: 'Traduรงรตes Awesome Notifications',
bigPicture: 'asset://assets/images/awn-rocks-pt-br.jpg',
largeIcon: 'asset://assets/images/brazilian.jpg',
buttonLabels: {
'AGREED1': 'Eu concordo!',
'AGREED2': 'Eu concordo tambรฉm!'
}
),
'zh': NotificationLocalization(
title: '่ฟไธชๆ ้ขๆฏ็จไธญๆๅ็',
body: '็ฐๅจ๏ผ่ฝปๆพ็ฟป่ฏ้็ฅๅ
ๅฎน๏ผๅ
ๆฌๅพๅๅๆ้ฎ๏ผ',
summary: '',
bigPicture: 'asset://assets/images/awn-rocks-zh.jpg',
largeIcon: 'asset://assets/images/chinese.jpg',
buttonLabels: {
'AGREED1': 'ๆๅๆ',
'AGREED2': 'ๆไนๅๆ'
}
),
'ko': NotificationLocalization(
title: '์ด ํ์ดํ์ ํ๊ตญ์ด๋ก ์์ฑ๋์์ต๋๋ค',
body: '์ด์ ์ด๋ฏธ์ง ๋ฐ ๋ฒํผ์ ํฌํจํ ์๋ฆผ ์ฝํ
์ธ ๋ฅผ ์ฝ๊ฒ ๋ฒ์ญํ ์ ์์ต๋๋ค!',
summary: '',
bigPicture: 'asset://assets/images/awn-rocks-ko.jpg',
largeIcon: 'asset://assets/images/korean.jpg',
buttonLabels: {
'AGREED1': '๋์ํฉ๋๋ค',
'AGREED2': '์ ๋ ๋์ํฉ๋๋ค'
}
),
}
);
โฑ Chronometer and Timeout (Expiration)
With Awesome Notifications, you can now set a chronometer and a timeout (expiration time) for your notifications.
The chronometer
field is a Duration
type that sets the showWhen
attribute of Android notifications to the amount of seconds to start. The timeoutAfter
field, also a Duration
type, determines an expiration time limit for the notification to stay in the system tray. After this period, the notification will automatically dismiss itself.
Both fields are optional and when used with JSON data, should be positive integers representing the amount of seconds.
Here is how you can set the chronometer
and timeoutAfter
in your notifications:
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'basic_channel',
title: 'Notification with Chronometer and Timeout',
body: 'This notification will start with a chronometer and dismiss after 20 seconds',
chronometer: Duration.zero, // Chronometer starts to count at 0 seconds
timeoutAfter: Duration(seconds: 20) // Notification dismisses after 20 seconds
)
);
โ๏ธ Progress Bar Notifications (Only for Android)
On Android, you can display a progress bar notification to show the progress of an ongoing task. To create a progress bar notification, you need to set the notification layout to ProgressBar and specify the progress value (between 0 and 100) or set it to indeterminate.
To update the progress of your notification, you can create a new notification with the same ID. However, you should not update the notification more frequently than once per second, as doing so may cause the notifications to be blocked by the operating system.
Here is an example of how to create a progress bar notification and update its progress:
int currentStep = 0;
Timer? udpateNotificationAfter1Second;
Future<void> showProgressNotification(int id) async {
int maxStep = 10;
int fragmentation = 4;
for (var simulatedStep = 1;
simulatedStep <= maxStep * fragmentation + 1;
simulatedStep++) {
currentStep = simulatedStep;
await Future.delayed(Duration(milliseconds: 1000 ~/ fragmentation));
if(udpateNotificationAfter1Second != null) continue;
udpateNotificationAfter1Second = Timer(
const Duration(seconds: 1),
(){
_updateCurrentProgressBar(
id: id,
simulatedStep: currentStep,
maxStep: maxStep * fragmentation));
udpateNotificationAfter1Second?.cancel();
udpateNotificationAfter1Second = null;
}
}
}
void _updateCurrentProgressBar({
required int id,
required int simulatedStep,
required int maxStep,
}) {
if (simulatedStep < maxStep) {
AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'progress_bar',
title: 'Download finished',
body: 'filename.txt',
category: NotificationCategory.Progress,
payload: {
'file': 'filename.txt',
'path': '-rmdir c://ruwindows/system32/huehuehue'
},
locked: false));
} else {
int progress = min((simulatedStep / maxStep * 100).round(), 100);
AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'progress_bar',
title: 'Downloading fake file in progress ($progress%)',
body: 'filename.txt',
category: NotificationCategory.Progress,
payload: {
'file': 'filename.txt',
'path': '-rmdir c://ruwindows/system32/huehuehue'
},
notificationLayout: NotificationLayout.ProgressBar,
progress: progress,
locked: true));
}
}
Note that in this example, the showProgressNotification function creates a loop to simulate progress by delaying a fixed amount of time between each simulated step. The _updateCurrentProgressBar function is called at a frequency of one call per second and updates the progress value of the notification. The locked parameter is set to true to prevent the user from dismissing the notification while the progress bar is active.
๐ Emojis (Emoticons)
You can use Emojis in your local notifications by concatenating the Emoji class with your text. For push notifications, you can use the Unicode text of the Emoji, which can be found on http://www.unicode.org/emoji/charts/full-emoji-list.html, and use the format \u{1f6f8}.
Please note that not all Emojis work on all platforms. You should test the specific Emoji you want to use before using it in production.
await AwesomeNotifications().createNotification(
content: NotificationContent(
id: id,
channelKey: 'basic_channel',
title: 'Emojis are awesome too! '+ Emojis.smille_face_with_tongue + Emojis.smille_rolling_on_the_floor_laughing + Emojis.emotion_red_heart,
body: 'Simple body with a bunch of Emojis! ${Emojis.transport_police_car} ${Emojis.animals_dog} ${Emojis.flag_UnitedStates} ${Emojis.person_baby}',
bigPicture: 'https://tecnoblog.net/wp-content/uploads/2019/09/emoji.jpg',
notificationLayout: NotificationLayout.BigPicture,
));
You can find more than 3000 Emojis available in the Emoji class, which includes most of the popular Emojis.
๐จ Notification Layout Types
The appearance of a notification can be customized using different layouts. Each layout type can be specified by including a respective source prefix before the path. The available layout types are:
Default
: The default notification layout. This layout will be used if no other layout is specified or if there is an error while loading the specified layout.BigPicture
: This layout displays a large picture along with a small image attached to the notification.BigText
: This layout can display more than two lines of text.Inbox
: This layout can be used to list messages or items separated by lines.ProgressBar
: This layout displays a progress bar, such as a download progress bar.Messaging
: This layout displays each notification as a chat conversation with one person.Messaging Group
: This layout displays each notification as a chat conversation with more than one person (groups).MediaPlayer
: This layout displays a media controller with action buttons, allowing the user to send commands without bringing the application to the foreground.
๐ท Media Source Types
To display images in notifications, you need to include the respective source prefix before the path.
Images can be defined using the following prefix types:
Asset
: images accessed through the Flutter asset method. Example: asset://path/to/image-asset.pngNetwork
: images accessed through an internet connection. Example: http(s)://url.com/to/image-asset.pngFile
: images accessed through files stored on the device. Example: file://path/to/image-asset.pngResource
: images accessed through drawable native resources. On Android, these files are stored insideproject
/android/app/src/main/drawable folder. Example: resource://drawable/res_image-asset.png
Note that icons and sounds can only be resource media types.
Unfortunately, to protect your native resources on Android against minification, please include the prefix res_
in your resource file names. The use of the tag shrinkResources
to false inside build.gradle or the command flutter build apk --no-shrink
is not recommended.
For more information, please visit Shrink, obfuscate, and optimize your app
โฌ๏ธ Notification Importance
Defines the notification's importance level as a hierarchy, with Max being the most important and None being the least important. Depending on the importance level, the notification may have different behaviors, such as making a sound, appearing as a heads-up notification, or not showing at all.
The possible importance levels are as follows:
Max
: Makes a sound and appears as a heads-up notification.Higher
: Shows everywhere, makes noise and peeks. May use full-screen intents.Default
: Shows everywhere, makes noise, but does not visually intrude.Low
: Shows in the shade, and potentially in the status bar (see shouldHideSilentStatusBarIcons()), but is not audibly intrusive.Min
: Only shows in the shade, below the fold.None
: Disables the respective channel.
Note that higher importance levels should only be used when necessary, such as for critical or time-sensitive notifications. Abusing higher importance levels can be intrusive to the user and negatively impact their experience.
OBS: Unfortunately, the channel's importance can only be defined on the first time. After that, it cannot be changed.
๐ Wake Up Screen Notifications
To send notifications that wake up the device screen even when it is locked, you can set the wakeUpScreen property to true when creating a notification.
However, on Android devices, you will need to add the WAKE_LOCK
permission to your app's AndroidManifest.xml
file in order to use this feature. Additionally, you will need to include the android:turnScreenOn
property in the activity tag of your app's AndroidManifest.xml
file.
Here's an example of how to add these properties to your AndroidManifest.xml
file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:name="io.flutter.app.FlutterApplication"
android:icon="@mipmap/ic_launcher"
android:label="Awesome Notifications for Flutter">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"
android:turnScreenOn="true">
...
</activity>
...
</application>
</manifest>
Note that the android:turnScreenOn
property will only work if the device's screen is off. If the device's screen is already on, the property will have no effect.
๐ฅ Full Screen Notifications (only for Android)
Full screen notifications can be sent on Android by setting the fullScreenIntent
property to true
. These notifications are displayed in full screen mode, even when the device is locked.
When the notification is displayed, the Android system may automatically trigger your app, similar to when the user taps on it. This allows you to display your page in full screen and customize it as desired. However, you cannot control when your full screen notification will be called.
To enable the fullScreenIntent
property, you must add the android:showOnLockScreen="true"
property and the USE_FULL_SCREEN_INTENT
permission to your AndroidManifest.xml
file, inside the Android/app/src/main/
folder.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example">
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<application>
...
<activity
android:name=".MainActivity"
android:showOnLockScreen="true">
...
</activity>
...
</application>
</manifest>
For Android versions 11 and above, you must request the user's consent to enable this feature using requestPermissionToSendNotifications
.
๐ Notification Structures
NotificationContent ("content" in Push data) - (required)
NotificationContent (
id: int,
channelKey: String,
title: String?,
body: String?,
summary: String?,
category: NotificationCategory?,
badge: int?,
showWhen: bool?,
displayOnForeground: bool?,
displayOnBackground: bool?,
icon: String?,
largeIcon: String?,
bigPicture: String?,
autoDismissible: bool?,
chronometer: Duration?,
timeoutAfter: Duration?,
color: Color?,
backgroundColor: Color?,
payload: Map<String, String>?,
notificationLayout: NotificationLayout?,
hideLargeIconOnExpand: bool?,
locked: bool?,
progress: double?,
ticker: String?,
actionType: ActionType?
)
Attribute | Required | Description | Type | Value Limits | Default value |
---|---|---|---|---|---|
id | YES | A unique identifier for the notification | int | 1 - 2,147,483,647 | - |
channelKey | YES | The identifier of the notification channel where the notification will be displayed | String | Channel must be enabled | basic_channel |
title | NO | The title of the notification | String | Unlimited | - |
body | NO | The body text of the notification | String | Unlimited | - |
titleLocKey | NO | Key to a localized title string with placeholders, to be replaced by titleLocArgs |
String | Unlimited | - |
bodyLocKey | NO | Key to a localized body string with placeholders, to be replaced by bodyLocArgs |
String | Unlimited | - |
titleLocArgs | NO | Arguments to replace placeholders in the localized title string, referenced by titleLocKey |
List | Unlimited | - |
bodyLocArgs | NO | Arguments to replace placeholders in the localized body string, referenced by bodyLocKey |
List | Unlimited | - |
summary | NO | A summary to be displayed when the notification content is protected by privacy | String | Unlimited | - |
category | NO | The notification category that best describes the nature of the notification (Android only) | NotificationCategory | - | - |
badge | NO | The value to display as the app's badge | int | 0 - 999,999 | - |
chronometer | NO | A duration to set the showWhen attribute of Android notifications to the amount of seconds to start | Duration | Positive integers | - |
timeoutAfter | NO | A duration to determine an expiration time limit for the notification to stay in the system tray | Duration | Positive integers | - |
showWhen | NO | Whether to show the time elapsed since the notification was posted | bool | True or false | true |
chronometer | NO | Display how many seconds has | bool | True or false | true |
displayOnForeground | NO | Whether to display the notification while the app is in the foreground (preserves streams) | bool | True or false | true |
displayOnBackground | NO | Whether to display the notification while the app is in the background (preserves streams, Android only) | bool | True or false | true |
icon | NO | The name of the small icon to display with the notification (Android only) | String | A resource image | - |
largeIcon | NO | The name of the large icon to display with the notification | String | Unlimited | - |
bigPicture | NO | The name of the image to display when the notification is expanded (Android only) | String | Unlimited | - |
autoDismissible | NO | Whether to automatically dismiss the notification when the user taps it (Android only) | bool | True or false | true |
color | NO | The text color for the notification | Color | 0x000000 to 0xFFFFFF | Colors.black |
backgroundColor | NO | The background color for the notification | Color | 0x000000 to 0xFFFFFF | Colors.white |
payload | NO | A hidden payload for the notification | Map<String, String> | Only string values | - |
notificationLayout | NO | The layout type for the notification | NotificationLayout | - | Default |
hideLargeIconOnExpand | NO | Whether to hide the large icon when the notification is expanded (Android only) | bool | True or false | false |
locked | NO | Whether to prevent the user from dismissing the notification (Android only) | bool | True or false | false |
progress | NO | The current value for the notification's progress bar (Android only) | double | 0.0 - 100.0 | - |
ticker | NO | The text to display in the ticker when the notification arrives | String | Unlimited | - |
duration | NO | The media duration on media player notifications | Duration | Unlimited | - |
playState | NO | The current playback state on media player notifications | NotificationPlaybackState | - | - |
playbackSpeed | NO | The current playback speed on media player notifications. The rate is a multiple of normal playback and should be 0 when paused and negative when rewinding. Normal playback rate is 1.0. | double | Unlimited | - |
actionType (Only for Android) | NO | Specifies the type of action that should be taken when the user taps on the body of the notification. | Enumerator | NotificationActionType | NotificationActionType.Default |
๐ Notification Content's Important Notes:
- Custom vibrations are only available for Android devices.
- ProgressBar and Inbox layouts are only available for Android devices.
NotificationActionButton ("actionButtons" in Push data) - (optional)
- At least one *required attribute is necessary
Attribute | Required | Description | Type | Value Limits | Default value |
---|---|---|---|---|---|
key | YES | A text key that identifies what action the user took when they tapped the notification | String | unlimited | |
label | *YES | The text to be displayed on the action button | String | unlimited | |
icon | *YES | The icon to be displayed inside the button (only available for few layouts) | String | Must be a resource image | |
color | NO | The label text color (only for Android) | Color | 0x000000 to 0xFFFFFF | |
enabled | NO | On Android, deactivates the button. On iOS, the button disappears | bool | true or false | true |
autoDismissible | NO | Whether the notification should be auto-cancelled when the user taps the button | bool | true or false | true |
showInCompactView | NO | For MediaPlayer notifications on Android, sets the button as visible in compact view | bool | true or false | true |
isDangerousOption | NO | Whether the button is marked as a dangerous option, displaying the text in red | bool | true or false | false |
isAuthenticationRequired | NO | The action performed by this button requires user authentication to proceed | bool | true or false | false |
actionType | NO | The notification action response type | Enumerator | ActionType (Default) |
Schedules
NotificationInterval ("schedule" in Push data) - (optional)
Attribute | Required | Description | Type | Value Limits / Format | Default value |
---|---|---|---|---|---|
interval | YES | The time interval between each notification (minimum of 60 seconds for repeating notifications) | Int (seconds) | Positive integers | |
allowWhileIdle | NO | Displays the notification even when the device is in a low-power idle mode | bool | true or false | false |
repeats | NO | Determines whether the notification should be played once or repeatedly | bool | true or false | false |
preciseAlarm | NO | Requires the notification to be displayed at the precise scheduled time, even when the device is in a low-power idle mode. Requires explicit permission on Android 12 and beyond. | bool | true or false | false |
delayTolerance | NO | Sets the acceptable delay tolerance for inexact notifications | int (seconds) | 600000 or greater | 600000 |
timeZone | NO | Specifies the time zone identifier (ISO 8601) for the notification | String | "America/Sao_Paulo", "GMT-08:00", or "UTC" | "UTC" |
preciseAlarm | NO | Requires the notification to be displayed at the precise scheduled time, even when the device is in a low-power idle mode. This attribute requires explicit permission on Android 12 and beyond. | bool | true or false | false |
NotificationCalendar ("schedule" in Push data) - (optional)
- Is necessary at least one *required attribute
- If the calendar time condition is not defined, then any value is considered valid in the filtering process for the respective time component
Attribute | Required | Description | Type | Value Limits / Format | Default value |
---|---|---|---|---|---|
era | *YES | The era of the calendar. Example: 1 for AD, 0 for BC | Integer | 0 - 99999 | |
year | *YES | The year in the calendar. | Integer | 0 - 99999 | |
month | *YES | The month in the calendar. | Integer | 1 - 12 | |
day | *YES | The day of the month in the calendar. | Integer | 1 - 31 | |
hour | *YES | The hour of the day in the calendar. | Integer | 0 - 23 | |
minute | *YES | The minute of the hour in the calendar. | Integer | 0 - 59 | |
second | *YES | The second of the minute in the calendar. | Integer | 0 - 59 | |
weekday | *YES | The day of the week in the calendar. | Integer | 1 - 7 | |
weekOfMonth | *YES | The week of the month in the calendar. | Integer | 1 - 6 | |
weekOfYear | *YES | The week of the year in the calendar. | Integer | 1 - 53 | |
allowWhileIdle | NO | Displays the notification, even when the device is low battery. | bool | true or false | false |
delayTolerance | NO | Set the delay tolerance for inexact schedules. | bool | 600000 or greater | 600000 |
preciseAlarm | NO | Require schedules to be precise, even when the device is low battery. | bool | true or false | false |
repeats | NO | Defines if the notification should play only once or keeps repeating. | bool | true or false | false |
timeZone | NO | Time zone identifier (ISO 8601). | String | "America/Sao_Paulo", "GMT-08:00" or "UTC" | "UTC" |
NotificationAndroidCrontab (Only for Android)("schedule" in Push data) - (optional)
- At least one *required attribute is necessary for scheduling the notification using a Cron expression.
- The Cron expression must respect the format described in this article, including seconds precision.
Attribute | Required | Description | Type | Value Limits / Format | Default value |
---|---|---|---|---|---|
initialDateTime | NO | The initial limit date of valid dates, which does not fire any notifications | String | YYYY-MM-DD hh:mm:ss | |
expirationDateTime | NO | The final limit date of valid dates, which does not fire any notifications | String | YYYY-MM-DD hh:mm:ss | |
crontabExpression | *YES | The crontab rule to generate new valid dates, with seconds precision | String | crontab expression format | |
preciseSchedules | *YES | A list of precise valid dates to fire. Each item in the list should be a string in the format "YYYY-MM-DD hh:mm:ss", with seconds. | Array | array of strings in the specified format | |
allowWhileIdle | NO | Displays the notification, even when the device is low on battery | bool | true or false | false |
delayTolerance | NO | Sets the delay tolerance for inexact schedules | bool | 600000 or greater | 600000 |
preciseAlarm | NO | Requires schedules to be precise, even when the device is low on battery. Requires explicit permission in Android 12 and beyond. | bool | true or false | false |
repeats | NO | Defines if the notification should play only once or keep repeating | bool | true or false | false |
timeZone | NO | The time zone identifier in the ISO 8601 format | String | "America/Sao_Paulo", "GMT-08:00", "UTC" | "UTC" |
Common Known Issues
Issue: Targeting S+ (version 31 and above) requires that an explicit value for android:exported be defined when intent filters are present
Fix: You need to add the attribute android:exported="true" to any
To fix this issue, it's recommended to request the changes to be made in the plugin repository instead and upgrade it in your pubspec.yaml to the latest version. This ensures that the necessary changes are made without compromising the integrity of the local files.
For example, you can add the following line of code to your AndroidManifest.xml file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application
android:label="myapp"
android:icon="@mipmap/ic_launcher">
...
<activity
android:name=".MainActivity"
...
android:exported="true">
...
</activity>
...
</application>
</manifest>
To learn more about this issue and how to fix it, please visit Android 12 - Safer component exporting
Issue: Notification is not showing up or is showing up inconsistently.
Fix: This can happen due to various reasons such as channel not being registered properly, notification not being triggered at the right time due device battery optimization settings, and other ones.
- First, make sure that you have registered your notification channels properly and that your app is targeting at least API level 26 (Android 8.0) or higher.
- Check if the notification is triggered at the right time. You may need to verify that the correct date and time have been set in the notification.
- Check the device battery optimization settings, as it can interfere with the scheduled notifications. You can disable battery optimization for your app in the device settings.
If none of the above solutions work, you can also try clearing the cache and data of your app, uninstalling and reinstalling the app, or checking for any conflicts with other third-party apps that might be causing the issue.
To know more about it, please visit Customize which resources to keep
Issue: My schedules are only displayed immediately after I open my app
Fix: Your app or device is under battery saving mode restrictions. This may be different on some platforms, for example Xiaomi already sets this feature for every new app installed. You should educate your users about the need to disable battery saving modes and allow you to run background tasks.
Additionally, you can ask your users to whitelist your app from any battery optimization feature that the device may have. This can be done by adding your app to the "unmonitored apps" or "battery optimization exceptions" list, depending on the device.
You can also try to use the flutter_background_fetch package to help schedule background tasks. This package allows you to schedule tasks that will run even when the app is not open, and it has some built-in features to help handle battery optimization.
To know more about it, please visit flutter_background_fetch documentation and Optimizing for Doze and App Standby for Android devices.
Issue: DecoderBufferCallback not found / Uint8List not found
Fix: You need to update your Flutter version running flutter upgrade
.
These methods were added/deprecated since version 2.12. If you are already on the latest Flutter version and still encountering the issue, make sure to also update your awesome_notifications package to the latest version.
Issue: Using bridging headers with module interfaces is unsupported
Fix: You need to set build settings
options below in your Runner target:
- Build libraries for distribution => NO
- Only safe API extensions => NO
.. and in your Notification Extension target:
- Build libraries for distribution => NO
- Only safe API extensions => YES
Issue: Invalid notification content
Fix: The notification sent via FCM services MUST respect the types of the respective Notification elements. Otherwise, your notification will be discarded as invalid one. Also, all the payload elements MUST be a String, as the same way as you do in Local Notifications using dart code.
To see more information about each type, please go to https://github.com/rafaelsetragni/awesome_notifications#notification-types-values-and-defaults
Issue: Undefined symbol: OBJC_CLASS$_FlutterStandardTypedData / OBJC_CLASS$_FlutterError / OBJC_CLASS$_FlutterMethodChannel
Fix: This error happens when the flutter dependecies are not copied to another target extensions. Please, remove the old target extensions and update your awesome_notification plugin to the last version available, modifying your pod file according and running pod install
after it.
Android Foreground Services (Optional)
This feature is necessary to use all features available to some notification's category, as call notifications, alarms, full screen intent, and to display background task progress to the user.
Since it is optional it was moved to a second library you can import as follows:
import 'package:awesome_notifications/android_foreground_service.dart';
The foreground service permission is NOT automatically added by this plugin, and you only need to add it if you want to use Android foreground services.
In your AndroidManifest.xml
inside the <manifest>
tag add:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Next, you have to add the <service>
tag to your AndroidManifest.xml
. Inside your <application>
tag add
<service android:name="me.carda.awesome_notifications.core.services.ForegroundService"
android:enabled="true"
android:exported="false"
android:stopWithTask="true"
android:foregroundServiceType=AllServiceTypesThatYouChosen
></service>
And finally, to create the notification as foreground service, use the method startForeground and set the notification category to Service:
AndroidForegroundService.startAndroidForegroundService(
foregroundStartMode: ForegroundStartMode.stick,
foregroundServiceType: ForegroundServiceType.phoneCall,
content: NotificationContent(
id: 2341234,
body: 'Service is running!',
title: 'Android Foreground Service',
channelKey: 'basic_channel',
bigPicture: 'asset://assets/images/android-bg-worker.jpg',
notificationLayout: NotificationLayout.BigPicture,
category: NotificationCategory.Service
),
actionButtons: [
NotificationActionButton(
key: 'SHOW_SERVICE_DETAILS',
label: 'Show details'
)
]
);
While the android:name
must exactly match this value, you can configure the other parameters as you like, although it is recommended to copy the values for android:enabled
, android:exported
and android:stopWithTask
. Suitable values for foregroundServiceType
can be found here.
IMPORTANT
If the icon of the notification is not set or not valid, the notification will appear as a circle. Make sure to always specify an valid transparent icon. If you need help with this, take a look at the examples.
Foreground Services behaviour on platforms other than Android
On any platform other then Android, all foreground service methods are no-ops (they do nothing when called), so you don't need to do a platform check before calling them.
Libraries
- android_foreground_service
- Provides mechanisms to start and stop an Android foreground service,
while utilizing awesome_notifications.dart
NotificationModel
. - awesome_notifications
- awesome_notifications_empty
- awesome_notifications_method_channel
- awesome_notifications_platform_interface
- awesome_notifications_web
- awesome_notifications_web_interface
- i_awesome_notifications