diff --git a/flutter_local_notifications/README.md b/flutter_local_notifications/README.md index b3eca84eb..ddbcc4984 100644 --- a/flutter_local_notifications/README.md +++ b/flutter_local_notifications/README.md @@ -5,984 +5,127 @@ A cross platform plugin for displaying local notifications. ->[!IMPORTANT] -> Given how quickly the Flutter ecosystem evolves, the minimum Flutter SDK version will be bumped occasionally to make it easier to maintain the plugin. Note that the official plugins already follow a similar approach. If this affects your applications (e.g., you need to support an older OS version), you may need to consider maintaining your own fork. +> [!IMPORTANT] +> +> **Local Notifications** are very different than **Push Notifications**! Push notifications, as offered by Firebase Cloud Messaging and Apple's Push Notifications Service, are sent from the server to your users directly, while local notifications are generated by the app on the user's device and not from your server. +> +> **This package only supports local notifications**. Please do not open issues about push notifications. Note, however, if you want to use this package for push notifications, you can configure your push notification code to intercept the data *without* sending a notification, then use this plugin to actually show the notification, with all the customization options this plugin supports. -## Table of contents +## Setting Up -- **[📱 Supported platforms](#-supported-platforms)** -- **[✨ Features](#-features)** -- **[⚠ Caveats and limitations](#-caveats-and-limitations)** - - [Compatibility with firebase_messaging](#compatibility-with-firebase_messaging) - - [Scheduled Android notifications](#scheduled-android-notifications) - - [iOS pending notifications limit](#ios-pending-notifications-limit) - - [Scheduled notifications and daylight saving time](#scheduled-notifications-and-daylight-saving-time) - - [Updating application badge](#updating-application-badge) - - [Custom notification sounds](#custom-notification-sounds) - - [macOS differences](#macos-differences) - - [Linux limitations](#linux-limitations) - - [Notification payload](#notification-payload) -- **[📷 Screenshots](#-screenshots)** -- **[👏 Acknowledgements](#-acknowledgements)** -- **[🔧 Android Setup](#-android-setup)** - - [Gradle setup](#gradle-setup) - - [AndroidManifest.xml setup](#androidmanifestxml-setup) - - [Requesting permissions on Android 13 or higher](#requesting-permissions-on-android-13-or-higher) - - [Custom notification icons and sounds](#custom-notification-icons-and-sounds) - - [Scheduled notifications](#scheduling-a-notification) - - [Fullscreen intent notifications](#full-screen-intent-notifications) - - [Release build configuration](#release-build-configuration) - - [ProGuard rules](#proguard-rules) -- **[🔧 iOS setup](#-ios-setup)** - - [General setup](#general-setup) - - [Handling notifications whilst the app is in the foreground](#handling-notifications-whilst-the-app-is-in-the-foreground) -- **[❓ Usage](#-usage)** - - [Notification Actions](#notification-actions) - - [Example app](#example-app) - - [API reference](#api-reference) -- **[Initialisation](#initialisation)** - - [[iOS (all supported versions) and macOS 10.14+] Requesting notification permissions](#ios-all-supported-versions-and-macos-1014-requesting-notification-permissions) - - [Displaying a notification](#displaying-a-notification) - - [Scheduling a notification](#scheduling-a-notification) - - [Periodically show a notification with a specified interval](#periodically-show-a-notification-with-a-specified-interval) - - [Retrieving pending notification requests](#retrieving-pending-notification-requests) - - [[Selected OS versions] Retrieving active notifications](#android-only-retrieving-active-notifications) - - [Grouping notifications](#grouping-notifications) - - [Cancelling/deleting a notification](#cancellingdeleting-a-notification) - - [Cancelling/deleting all notifications](#cancellingdeleting-all-notifications) - - [Getting details on if the app was launched via a notification created by this plugin](#getting-details-on-if-the-app-was-launched-via-a-notification-created-by-this-plugin) - - [[iOS only] Periodic notifications showing up after reinstallation](#ios-only-periodic-notifications-showing-up-after-reinstallation) -- **[📈 Testing](#-testing)** +This plugin has **a lot** of small details to get right. It's recommended to read, configure, and test one platform at a time before moving onto the next, as each platform has its own set of nuances. Each supported platform is listed in the table below. To get started: -## 📱 Supported platforms +1. Some platforms need special setup at build time. Read the Setup Guide for your platform +2. Read the [General Usage Guide](./docs/usage.md) (relevant for all platforms) +3. Read the Usage Guide for your platform -* **Android+**. Uses the [NotificationCompat APIs](https://developer.android.com/reference/androidx/core/app/NotificationCompat) so it can be run older Android devices -* **iOS** Uses the [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework) -* **macOS** Uses the [UserNotification APIs](https://developer.apple.com/documentation/usernotifications) (aka the User Notifications Framework) -* **Linux**. Uses the [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/) -* **Windows** Uses the [C++/WinRT](https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/) implementation of [Toast Notifications](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-notifications-overview) +| Platform | Minimum Supported Version | APIs Used | Setup Guide | Usage Guide | +| -------- | ------------------------- | ------------------------------------------------------------ | ---------------------------------------- | ---------------------------------------- | +| Android | Lollipop (API Level 21) | [`NotificationCompat`](https://developer.android.com/reference/androidx/core/app/NotificationCompat) | [Android Setup](./docs/android-setup.md) | [Android Usage](./docs/android-usage.md) | +| iOS | iOS 12 | [`UserNotification`](https://developer.apple.com/documentation/usernotifications) | [iOS Setup](./docs/ios-setup.md) | [Darwin Usage](./docs/darwin-usage.md) | +| MacOS | Mojave (MacOS 10.14) | [`UserNotification`](https://developer.apple.com/documentation/usernotifications) | N/A | [Darwin Usage](./docs/darwin-usage.md) | +| Linux | Ubuntu 20.04, Debian 9 | [FreeDesktop Notifications](https://specifications.freedesktop.org/notification-spec/latest/) | N/A | [Linux Usage](./docs/linux-usage.md) | +| Windows | Windows 10 | [`ToastNotification`](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-notifications-overview) | [Windows Setup](./docs/windows-setup.md) | [Windows Usage](./docs/windows-usage.md) | -Note: the plugin requires Flutter SDK 3.22 at a minimum. The list of support platforms for Flutter 3.22 itself can be found [here](https://github.com/flutter/website/blob/4fa26a1e909a2243fa18e4d101192bb5d400fcf2/src/_data/platforms.yml) +## Simple usage -## ✨ Features +Every platform has different features, but here is a very brief overview of the basics, using Android as an example: -* Mockable (plugin and API methods aren't static) -* Display basic notifications -* Scheduling when notifications should appear -* Periodically show a notification (interval based) -* Schedule a notification to be shown daily at a specified time -* Schedule a notification to be shown weekly on a specified day and time -* Retrieve a list of pending notification requests that have been scheduled to be shown in the future -* Cancelling/removing notification by id or all of them -* Specify a custom notification sound -* Ability to handle when a user has tapped on a notification, when the app is in the foreground, background or is terminated -* Determine if an app was launched due to tapping on a notification -* [Android] Request permission to show notifications -* [Android] Configuring the importance level -* [Android] Configuring the priority -* [Android] Customising the vibration pattern for notifications -* [Android] Configure the default icon for all notifications -* [Android] Configure the icon for each notification (overrides the default when specified) -* [Android] Configure the large icon for each notification. The icon can be a drawable or a file on the device -* [Android] Formatting notification content via [HTML markup](https://developer.android.com/guide/topics/resources/string-resource.html#StylingWithHTML) -* [Android] Support for the following notification styles - * Big picture - * Big text - * Inbox - * Messaging - * Media - * While media playback control using a `MediaSession.Token` is not supported, with this style you let Android treat the `largeIcon` bitmap as album artwork -* [Android] Group notifications -* [Android] Show progress notifications -* [Android] Configure notification visibility on the lockscreen -* [Android] Ability to create and delete notification channels -* [Android] Retrieve the list of active notifications -* [Android] Full-screen intent notifications -* [Android] Start a foreground service -* [Android] Ability to check if notifications are enabled -* [iOS (all supported versions) & macOS 10.14+] Request notification permissions and customise the permissions being requested around displaying notifications -* [iOS 10 or newer and macOS 10.14 or newer] Display notifications with attachments -* [iOS and macOS 10.14 or newer] Ability to check if notifications are enabled with specific type check -* [Linux] Ability to to use themed/Flutter Assets icons and sound -* [Linux] Ability to to set the category -* [Linux] Configuring the urgency -* [Linux] Configuring the timeout (depends on system implementation) -* [Linux] Ability to set custom notification location (depends on system implementation) -* [Linux] Ability to set custom hints -* [Linux] Ability to suppress sound -* [Linux] Resident and transient notifications -* [Windows] Can show raw XML (see the [Notifications Visualizer](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/notifications-visualizer)) -* [Windows] A full Dart API for all the options supported by toast notifications -* [Windows] Can configure images, buttons, dropdowns, text input, and launch behavior -* [Windows] Can dynamically update notifications after they've been shown - -## ⚠ Caveats and limitations - -The cross-platform facing API exposed by the `FlutterLocalNotificationsPlugin` class doesn't expose platform-specific methods as its goal is to provide an abstraction for all platforms. As such, platform-specific configuration is passed in as data. There are platform-specific implementations of the plugin that can be obtained by calling the [`resolvePlatformSpecificImplementation`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/resolvePlatformSpecificImplementation.html). An example of using this is provided in the section on requesting permissions on iOS. In spite of this, there may still be gaps that don't cover your use case and don't make sense to add as they don't fit with the plugin's architecture or goals. Developers can fork or maintain their own code for showing notifications in these situations. - -### Compatibility with firebase_messaging - -Previously, there were issues that prevented this plugin working properly with the `firebase_messaging` plugin. This meant that callbacks from each plugin might not be invoked. This has been resolved since version 6.0.13 of the `firebase_messaging` plugin so please make sure you are using more recent versions of the `firebase_messaging` plugin and follow the steps covered in `firebase_messaging`'s readme file located [here](https://pub.dev/packages/firebase_messaging) - -### Scheduled Android notifications - -Some Android OEMs have their own customised Android OS that can prevent applications from running in the background. Consequently, scheduled notifications may not work when the application is in the background on certain devices (e.g. by Xiaomi, Huawei). If you experience problems like this then this would be the reason why. As it's a restriction imposed by the OS, this is not something that can be resolved by the plugin. Some devices may have setting that lets users control which applications run in the background. The steps for these can vary but it is still up to the users of your application to do given it's a setting on the phone itself. The site https://dontkillmyapp.com provides details on how to do this for various devices. - -It has been reported that Samsung's implementation of Android has imposed a maximum of 500 alarms that can be scheduled via the [Alarm Manager](https://developer.android.com/reference/android/app/AlarmManager) API and exceptions can occur when going over the limit. - -### iOS pending notifications limit - -There is a limit imposed by iOS where it will only keep the 64 notifications that were last set on any iOS versions newer than 9. On iOS versions 9 and older, the 64 notifications that fire soonest are kept. [See here for more details.](https://ileyf.cn.openradar.appspot.com/38065340) - -### Scheduled notifications and daylight saving time - -The notification APIs used on iOS versions older than 10 (aka the `UILocalNotification` APIs) have limited supported for time zones. - -### Updating application badge - -This plugin doesn't provide APIs for directly setting the badge count for your application. If you need this for your application, there are other plugins available, such as the [`flutter_app_badger`](https://pub.dev/packages/flutter_app_badger) plugin. - -### Custom notification sounds - -[iOS and macOS restrictions](https://developer.apple.com/documentation/usernotifications/unnotificationsound?language=objc) apply (e.g. supported file formats). - -### macOS differences - -Due to limitations currently within the macOS Flutter engine, `getNotificationAppLaunchDetails` will return null on macOS versions older than 10.14. These limitations will mean that conflicts may occur when using this plugin with other notification plugins (e.g. for push notifications). - -The `schedule`, `showDailyAtTime` and `showWeeklyAtDayAndTime` methods that were implemented before macOS support was added and have been marked as deprecated aren't implemented on macOS. - -### Linux limitations - -Capabilities depend on the system notification server implementation, therefore, not all features listed in `LinuxNotificationDetails` may be supported. One of the ways to check some capabilities is to call the `LinuxFlutterLocalNotificationsPlugin.getCapabilities()` method. - -Scheduled/pending notifications is currently not supported due to the lack of a scheduler API. - -The `onDidReceiveNotificationResponse` callback runs on the main isolate of the running application and cannot be launched in the background if the application is not running. To respond to notification after the application is terminated, your application should be registered as DBus activatable (please see [DBusApplicationLaunching](https://wiki.gnome.org/HowDoI/DBusApplicationLaunching) for more information), and register action before activating the application. This is difficult to do in a plugin because plugins instantiate during application activation, so `getNotificationAppLaunchDetails` can't be implemented without changing the main user application. - -### Windows limitations - -- Windows does not support repeating notifications, so [`periodicallyShow`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/periodicallyShow.html) and [`periodicallyShowWithDuration`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/periodicallyShowWithDuration.html) will throw `UnsupportedError`s. -- Windows only allows apps with package identity to retrieve previously shown notifications. This means that on an app that was not packaged as an [MSIX](https://learn.microsoft.com/en-us/windows/msix/overview) installer, [`cancel`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/cancel.html) does nothing and [`getActiveNotifications`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/getActiveNotifications.html) will return an empty list. To package your app as an MSIX, see [`package:msix`](https://pub.dev/packages/msix) and the `msix` section in [the example's `pubspec.yaml`](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/pubspec.yaml). - -### Notification payload - -Due to some limitations on iOS with how it treats null values in dictionaries, a null notification payload is coalesced to an empty string behind the scenes on all platforms for consistency. - -## 📷 Screenshots - -| Platform | Screenshot | -| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Android | | -| iOS | | -| macOS | | -| Linux | | -| Windows | | - - -## 👏 Acknowledgements - -* [Javier Lecuona](https://github.com/javiercbk) for submitting the PR that added the ability to have notifications shown daily -* [Jeff Scaturro](https://github.com/JeffScaturro) for submitting the PR to fix the iOS issue around showing daily and weekly notifications and migrating the plugin to AndroidX -* [Ian Cavanaugh](https://github.com/icavanaugh95) for helping create a sample to reproduce the problem reported in [issue #88](https://github.com/MaikuB/flutter_local_notifications/issues/88) -* [Zhang Jing](https://github.com/byrdkm17) for adding 'ticker' support for Android notifications -* [Kenneth](https://github.com/kennethnym), [lightrabbit](https://github.com/lightrabbit), and [Levi Lesches](https://github.com/Levi-Lesches) for adding Windows support -* ...and everyone else for their contributions. They are greatly appreciated - -## 🔧 Android Setup - -Before proceeding, please make sure you are using the latest version of the plugin. Note that there have been differences in the setup depending on the version of the plugin used. Please make use of the release tags to refer back to older versions of readme. Applications that schedule notifications should pay close attention to the [AndroidManifest.xml setup](#androidmanifestxml-setup) section of the readme since Android 14 has brought about some behavioural changes. - -### Gradle setup - -Version 10+ on the plugin now relies on [desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) to support scheduled notifications with backwards compatibility on older versions of Android. Developers will need to update their application's Gradle file to enable desugaring even if they don't use scheduled notifications. Please see the link on desugaring for details but for convenience, you can expand below to see the relevant portions based on if your app has a `build.gradle` or `build.gradle.kts` file - -
-Groovy - build.gradle - -```gradle -android { - defaultConfig { - multiDexEnabled true - } - - compileOptions { - // Flag to enable support for the new language APIs - coreLibraryDesugaringEnabled true - // Sets Java compatibility to Java 11 - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = "11" - } -} - -dependencies { - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' -} -``` -
- -
-Kotlin - build.gradle.kts - -```kotlin -android { - defaultConfig { - multiDexEnabled = true - } - - compileOptions { - // Flag to enable support for the new language APIs - isCoreLibraryDesugaringEnabled = true - // Sets Java compatibility to Java 11 - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() - } -} - -dependencies { - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") -} -``` -
- -Note that the plugin uses Android Gradle plugin (AGP) 8.6.0 to leverage this functionality so to err on the safe side, applications should aim to use the same version at a **minimum**. If your application uses a higher version then there's no need to use a lower AGP version. For a Flutter app using the legacy `apply` script syntax, this is specified in `android/build.gradle` and the main parts would look similar to the following - -```gradle -buildscript { - ... - - dependencies { - classpath 'com.android.tools.build:gradle:8.6.0' - ... - } -``` - - -If your app is using the new declarative [Plugin DSL syntax](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply), the file to update will be either `android/settings.gradle` or `android/settings.gradle.kts` and will be similar to the following - -
-Groovy - settings.gradle - -```gradle -plugins { - ... - id 'com.android.application' version '8.6.0' apply false - ... -} -``` - -
- -
-Kotlin - settings.gradle.kts - -```kotlin -plugins { - ... - id("com.android.application") version "8.6.0" apply false - ... -} -``` - -
- -There have been reports that enabling desugaring may result in a Flutter apps crashing on Android 12L and above. This would be an issue with Flutter itself, not the plugin. One possible fix is adding the [WindowManager library](https://developer.android.com/jetpack/androidx/releases/window) as a dependency: - -
-Groovy - build.gradle - -```gradle -dependencies { - implementation 'androidx.window:window:1.0.0' - implementation 'androidx.window:window-java:1.0.0' - ... -} -``` - -
- -
-Kotlin - build.gradle.kts - -```gradle -dependencies { - implementation("androidx.window:window:1.0.0") - implementation("androidx.window:window-java:1.0.0") - ... -} -``` - -
- -More information and other proposed solutions can be found in [Flutter issue #110658](https://github.com/flutter/flutter/issues/110658). - -The plugin also requires that the `compileSdk` in your application's Gradle file is set to 35 at a minimum: - -
-Groovy - build.gradle - -```gradle -android { - compileSdk 35 - ... -} -``` - -
- -
-Kotlin - build.gradle.kts - -```kotlin -android { - compileSdk = 35 - ... -} -``` - -
- -### AndroidManifest.xml setup - -Previously the plugin would specify all the permissions required all of the features that the plugin support in its own `AndroidManifest.xml` file so that developers wouldn't need to do this in their own app's `AndroidManifest.xml` file. Since version 16 onwards, the plugin will now only specify the bare minimum and these [`POST_NOTIFICATIONS`](https://developer.android.com/reference/android/Manifest.permission#POST_NOTIFICATIONS) and [`VIBRATE`](https://developer.android.com/reference/android/Manifest.permission#VIBRATE) permissions. - -For apps that need the following functionality please complete the following in your app's `AndroidManifest.xml` - -* To schedule notifications the following changes are needed - * Specify the appropriate permissions between the `` tags. - * ``: this is required so the plugin can known when the device is rebooted. This is required so that the plugin can reschedule notifications upon a reboot - * If the app requires scheduling notifications with exact timings (aka exact alarms), there are two options since Android 14 brought about behavioural changes (see [here](https://developer.android.com/about/versions/14/changes/schedule-exact-alarms) for more details) - * specify `` and call the `requestExactAlarmsPermission()` exposed by the `AndroidFlutterNotificationsPlugin` class so that the user can grant the permission via the app or - * specify ``. Users will not be prompted to grant permission, however as per the official Android documentation on the `USE_EXACT_ALARM` permission (refer to [here](https://developer.android.com/about/versions/14/changes/schedule-exact-alarms#calendar-alarm-clock) and [here](https://developer.android.com/reference/android/Manifest.permission#USE_EXACT_ALARM)), this requires the app to target Android 13 (API level 33) or higher and could be subject to approval and auditing by the app store(s) used to publish theapp - * Specify the following between the `` tags so that the plugin can actually show the scheduled notification(s) - ```xml - - - - - - - - - - ``` -* To use full-screen intent notifications, specify the `` permission between the `` tags. Developers will also need to follow the instructions documented [here](#full-screen-intent-notifications) -* To use notification actions, specify `` between the `` tags so that the plugin can process the actions and trigger the appropriate callback(s) -* To use foreground services the following changes are needed - * [Request the appropriate permissions](https://developer.android.com/develop/background-work/services/foreground-services#request-foreground-service-permissions) - * Declare the service exposed by the plugin by adding the following between `` tags. An example of what this looks like is below where `` should be replaced with the foreground service type(s) your app needs. If you want your foreground service to be stopped if your app is stopped, set `android:stopWithTask` to `true` - ```xml - - ``` -* To be able to create channels that ignore the device's Do Not Disturb mode, specify the `` permission between the `` tags. Developers will also need to follow the instructions documented [here](#bypassing-do-not-disturb-dnd) - -Developers can refer to the example app's `AndroidManifest.xml` to help see what the end result may look like. Do note that the example app covers all the plugin's supported functionality so will request more permissions than your own app may need - -### Requesting permissions on Android 13 or higher - -From Android 13 (API level 33) onwards, apps now have the ability to display a prompt where users can decide if they want to grant an app permission to show notifications. For further reading on this matter read https://developer.android.com/guide/topics/ui/notifiers/notification-permission. To support this applications need target their application to Android 13 or higher and the compile SDK version needs to be at least 33 (Android 13). For example, to target Android 13, update your app's `build.gradle` file to have a `targetSdkVersion` of `33`. Applications can then call the following code to request the permission where the `requestPermission` method is associated with the `AndroidFlutterLocalNotificationsPlugin` class (i.e. the Android implementation of the plugin) - -``` -FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); -flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>().requestNotificationsPermission(); -``` - -### Custom notification icons and sounds - -Notification icons should be added as a drawable resource. The example project/code shows how to set default icon for all notifications and how to specify one for each notification. It is possible to use launcher icon/mipmap and this by default is `@mipmap/ic_launcher` in the Android manifest and can be passed `AndroidInitializationSettings` constructor. However, the offical Android guidance is that you should use drawable resources. Custom notification sounds should be added as a raw resource and the sample illustrates how to play a notification with a custom sound. Refer to the following links around Android resources and notification icons. - - * [Notifications](https://developer.android.com/studio/write/image-asset-studio#notification) - * [Providing resources](https://developer.android.com/guide/topics/resources/providing-resources) - * [Creating notification icon with Image Asset Studio](https://developer.android.com/studio/write/create-app-icons#create-notification) - -When specifying the large icon bitmap or big picture bitmap (associated with the big picture style), bitmaps can be either a drawable resource or file on the device. This is specified via a single property (e.g. the `largeIcon` property associated with the `AndroidNotificationDetails` class) where a value that is an instance of the `DrawableResourceAndroidBitmap` means the bitmap should be loaded from an drawable resource. If this is an instance of the `FilePathAndroidBitmap`, this indicates it should be loaded from a file referred to by a given file path. - -⚠️ For Android 8.0+, sounds and vibrations are associated with notification channels and can only be configured when they are first created. Showing/scheduling a notification will create a channel with the specified id if it doesn't exist already. If another notification specifies the same channel id but tries to specify another sound or vibration pattern then nothing occurs. - -### Full-screen intent notifications - -If your application needs the ability to schedule full-screen intent notifications, add the following attributes to the activity you're opening. For a Flutter application, there is typically only one activity extends from `FlutterActivity`. These attributes ensure the screen turns on and shows when the device is locked. - -```xml - -``` - -For reference, the example app's `AndroidManifest.xml` file can be found [here](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/src/main/AndroidManifest.xml). - -Note that when a full-screen intent notification actually occurs (as opposed to a heads-up notification that the system may decide should occur), the plugin will act as though the user has tapped on a notification so handle those the same way (e.g. `onDidReceiveNotificationResponse` callback) to display the appropriate page for your application. - -Developers should also be across Google's requirements on using full-screen intents. Please refer to their documentation [here](https://source.android.com/docs/core/permissions/fsi-limits) for more information. Should you app need request permissions, the `AndroidFlutterNotificationsPlugin` class exposes the `requestFullScreenIntentPermission()` method that can be used to do so. - -### Bypassing Do Not Disturb (DnD) - -If your application needs the ability to send notifications that ignore the device's DnD mode settings, you must request these permissions from Android 6.0 onwards. You can do this using `AndroidFlutterNotificationsPlugin` by calling the `requestNotificationPolicyAccess()` method which will redirect the user to a settings page where your application may be explicitly whitelisted to bypass DnD. **This method _must_ be called before attempting to create a notification channel with `bypassDnd: true`.** Failing to do so will cause the `bypassDnd` argument to be treated as `false`. - -For notifications to then actually ignore the DnD-status of a device, the channel must be created with `bypassDnd: true`, or the first notification on a channel that creates it must be sent with `channelBypassDnd: true`. - -**NOTE:** This does _not_ ignore the device's silent mode! Should you have a use case where you must notify your users (for instance in an emergency), you might want to use a package like [`sound_mode`](https://pub.dev/packages/sound_mode) or write your own platform-specific code to set the `RingerMode` of the device as well as change the notification stream's volume before and after the notification. - -### Release build configuration - -⚠️ Ensure that you have configured the resources that should be kept so that resources like your notification icons aren't discarded by the R8 compiler by following the instructions [here](https://developer.android.com/topic/performance/app-optimization/customize-which-resources-to-keep). If you have chosen to use `@mipmap/ic_launcher` as the notification icon (against the official Android guidance), be sure to include this in the `keep.xml` file too. If you do not ensure resources like the notification icon kept, notifications might be broken. In the worst case they will never show, instead silently failing when the system looks for a resource that has been removed. If they do still show, you might not see the icon you specified. The configuration used by the example app can be found [here](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/src/main/res/raw/keep.xml) where it is specifying that all drawable resources should be kept, as well as the file used to play a custom notification sound (sound file is located [here](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/src/main/res/raw/slow_spring_board.mp3)). - -#### ProGuard rules - -For flutter_local_notifications v19 and higher, the ProGuard rules are automatically provided by the GSON. The following documentation is for v18 and lower versions. - -Before creating the release build of your app (which is the default setting when building an APK or app bundle) you will need to customise your ProGuard configuration file as per this [link](https://developer.android.com/studio/build/shrink-code#keep-code). Rules specific to the GSON dependency being used by the plugin will need to be added. These rules can be found [here](https://github.com/google/gson/blob/main/examples/android-proguard-example/proguard.cfg). Whilst the example app has a Proguard rules (`proguard-rules.pro`) [here](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/proguard-rules.pro), it is recommended that developers refer to the rules on the GSON repository in case they get updated over time. - -## 🔧 iOS setup - -### General setup - -Add the following lines to the `application` method in the AppDelegate.m/AppDelegate.swift file of your iOS project. See an example of this [here](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/ios/Runner/AppDelegate.swift). - -Objective-C: -```objc -if (@available(iOS 10.0, *)) { - [UNUserNotificationCenter currentNotificationCenter].delegate = (id) self; -} -``` - -Swift: -```swift -if #available(iOS 10.0, *) { - UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate -} -``` - -### Handling notifications whilst the app is in the foreground - -By design, iOS applications *do not* display notifications while the app is in the foreground unless configured to do so. - -For iOS 10+, use the presentation options to control the behaviour for when a notification is triggered while the app is in the foreground. The default settings of the plugin will configure these such that a notification will be displayed when the app is in the foreground. - -## ❓ Usage - -Before going on to copy-paste the code snippets in this section, double-check you have configured your application correctly. -If you encounter any issues please refer to the API docs and the sample code in the `example` directory before opening a request on Github. - -### Notification Actions - -Notifications can now contain actions but note that on Apple's platforms, these work only on iOS 10 or newer and macOS 10.14 or newer. On macOS and Linux (see [Linux limitations](#linux-limitations) chapter), these will only run on the main isolate by calling the `onDidReceiveNotificationResponse` callback. On iOS and Android, these will run on the main isolate by calling the `onDidReceiveNotificationResponse` callback if the configuration has specified that the app/user interface should be shown i.e. by specifying the `DarwinNotificationActionOption.foreground` option on iOS and the `showsUserInterface` property on Android. If they haven't, then these actions may be selected by the user when an app is sleeping or terminated and will wake up your app. However, it may not wake up the user-visible part of your App; but only the part of it which runs in the background. This is done by spawning a background isolate. - -This plugin contains handlers for iOS & Android to handle these background isolate cases and will allow you to specify a Dart entry point (a function). -When the user selects a action, the plugin will start a **separate Flutter Engine** which will then invoke the `onDidReceiveBackgroundNotificationResponse` callback - -**Configuration**: - -*Android* and *Linux* do not require any configuration. - -*iOS* will require a few steps: - -Adjust `AppDelegate.m` and set the plugin registrant callback: - -If you're using Objective-C, add this function anywhere in AppDelegate.m: -``` objc -// This is required for calling FlutterLocalNotificationsPlugin.setPluginRegistrantCallback method. -#import -... -... -void registerPlugins(NSObject* registry) { - [GeneratedPluginRegistrant registerWithRegistry:registry]; -} -``` - -then extend `didFinishLaunchingWithOptions` and register the callback: - -``` objc -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [GeneratedPluginRegistrant registerWithRegistry:self]; - - // Add this method - [FlutterLocalNotificationsPlugin setPluginRegistrantCallback:registerPlugins]; -} -``` - -For Swift, open the `AppDelegate.swift` and update the `didFinishLaunchingWithOptions` as follows -where the commented code indicates the code to add in and why - -```swift -import UIKit -import Flutter -// This is required for calling FlutterLocalNotificationsPlugin.setPluginRegistrantCallback method. -import flutter_local_notifications - -@UIApplicationMain -override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - // This is required to make any communication available in the action isolate. - FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in - GeneratedPluginRegistrant.register(with: registry) - } - - ... - return super.application(application, didFinishLaunchingWithOptions: launchOptions) -} -``` - -On iOS/macOS, notification actions need to be configured before the app is started using the `initialize` method - -``` dart -final DarwinInitializationSettings initializationSettingsDarwin = DarwinInitializationSettings( - // ... - notificationCategories: [ - DarwinNotificationCategory( - 'demoCategory', - actions: [ - DarwinNotificationAction.plain('id_1', 'Action 1'), - DarwinNotificationAction.plain( - 'id_2', - 'Action 2', - options: { - DarwinNotificationActionOption.destructive, - }, - ), - DarwinNotificationAction.plain( - 'id_3', - 'Action 3', - options: { - DarwinNotificationActionOption.foreground, - }, - ), - ], - options: { - DarwinNotificationCategoryOption.hiddenPreviewShowTitle, - }, - ) -], -``` - -On iOS/macOS, the notification category will define which actions are availble. On Android and Linux, you can put the actions directly in the `AndroidNotificationDetails` and `LinuxNotificationDetails` classes. - -**Usage**: - -You need to configure a **top level** or **static** method which will handle the action: - -``` dart -@pragma('vm:entry-point') -void notificationTapBackground(NotificationResponse notificationResponse) { - // handle action -} -``` - -Specify this function as a parameter in the `initialize` method of this plugin: - -``` dart -await flutterLocalNotificationsPlugin.initialize( - initializationSettings, - onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) async { - // ... - }, - onDidReceiveBackgroundNotificationResponse: notificationTapBackground, -); -``` - -Remember this function runs (except Linux) in a separate isolate! This function also requires the `@pragma('vm:entry-point')` annotation to ensure that tree-shaking doesn't remove the code since it would be invoked on the native side. See [here](https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/aot/entry_point_pragma.md) for official documentation on the annotation. - -Developers should also note that whilst accessing plugins will work, on Android there is **no** access to the `Activity` context. This means some plugins (like `url_launcher`) will require additional flags to start the main `Activity` again. - -**Specifying actions on notifications**: - -The notification actions are platform-specific and you have to specify them differently for each platform. - -On iOS/macOS, the actions are defined on a category, please see the configuration section for details. - -On Android and Linux, the actions are configured directly on the notification. - -``` dart -Future _showNotificationWithActions() async { - const AndroidNotificationDetails androidNotificationDetails = - AndroidNotificationDetails( - '...', - '...', - '...', - actions: [ - AndroidNotificationAction('id_1', 'Action 1'), - AndroidNotificationAction('id_2', 'Action 2'), - AndroidNotificationAction('id_3', 'Action 3'), - ], - ); - const NotificationDetails notificationDetails = - NotificationDetails(android: androidNotificationDetails); - await flutterLocalNotificationsPlugin.show( - 0, '...', '...', notificationDetails); -} -``` - -Each notification will have a internal ID & an public action title. - -### Example app - -The [`example`](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications/example) directory has a sample application that demonstrates the features of this plugin. - -### API reference - -Checkout the lovely [API documentation](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/flutter_local_notifications-library.html) generated by pub. - -## Initialisation - -The first step is to create a new instance of the plugin class and then initialise it with the settings to use for each platform +### Initialize the application ```dart -FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); -// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project -const AndroidInitializationSettings initializationSettingsAndroid = - AndroidInitializationSettings('app_icon'); -final DarwinInitializationSettings initializationSettingsDarwin = - DarwinInitializationSettings(); -final LinuxInitializationSettings initializationSettingsLinux = - LinuxInitializationSettings( - defaultActionName: 'Open notification'); -final WindowsInitializationSettings initializationSettingsWindows = - WindowsInitializationSettings( - appName: 'Flutter Local Notifications Example', - appUserModelId: 'Com.Dexterous.FlutterLocalNotificationsExample', - // Search online for GUID generators to make your own - guid: 'd49b0314-ee7a-4626-bf79-97cdb8a991bb') -final InitializationSettings initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, - iOS: initializationSettingsDarwin, - macOS: initializationSettingsDarwin, - linux: initializationSettingsLinux, - windows: initializationSettingsWindows); -await flutterLocalNotificationsPlugin.initialize(initializationSettings, - onDidReceiveNotificationResponse: onDidReceiveNotificationResponse); -``` - -Initialisation can be done in the `main` function of your application or can be done within the first page shown in your app. Developers can refer to the example app that has code for the initialising within the `main` function. The code above has been simplified for explaining the concepts. Here we have specified the default icon to use for notifications on Android (refer to the *Android setup* section) and designated the function (`onDidReceiveNotificationResponse`) that should fire when a notification has been tapped on via the `onDidReceiveNotificationResponse` callback. Specifying this callback is entirely optional but here it will trigger navigation to another page and display the payload associated with the notification. This callback **cannot** be used to handle when a notification launched an app. Use the `getNotificationAppLaunchDetails` method when the app starts if you need to handle when a notification triggering the launch for an app e.g. change the home route of the app for deep-linking. - -Note that all settings are nullable, because we don't want to force developers so specify settings for platforms they don't target. You will get a runtime ArgumentError Exception if you forgot to pass the settings for the platform you target. - -```dart -void onDidReceiveNotificationResponse(NotificationResponse notificationResponse) async { - final String? payload = notificationResponse.payload; - if (notificationResponse.payload != null) { - debugPrint('notification payload: $payload'); - } - await Navigator.push( - context, - MaterialPageRoute(builder: (context) => SecondScreen(payload)), - ); +// 1. Define a handler to receive notification presses (when the app is running) +void onNotificationTapped(NotificationResponse response) { + final messageID = response.payload; + final actionID = response.actionId; + final userInput = response.input; + if (actionID == "reply") await myApp.respondToMessage(messageID, userInput); } -``` - -In the real world, this payload could represent the id of the item you want to display the details of. Once the initialisation is complete, then you can manage the displaying of notifications. Note that this callback is only intended to work when the app is running. For scenarios where your application needs to handle when a notification launched the app refer to [here](#getting-details-on-if-the-app-was-launched-via-a-notification-created-by-this-plugin) - -The `DarwinInitializationSettings` class provides default settings on how the notification be presented when it is triggered and the application is in the foreground on iOS/macOS. There are optional named parameters that can be modified to suit your application's purposes. Here, it is omitted and the default values for these named properties is set such that all presentation options (alert, sound, badge) are enabled. - -The `LinuxInitializationSettings` class requires a name for the default action that calls the `onDidReceiveNotificationResponse` callback when the notification is clicked. - -On iOS and macOS, initialisation may show a prompt to requires users to give the application permission to display notifications (note: permissions don't need to be requested on Android). Depending on when this happens, this may not be the ideal user experience for your application. If so, please refer to the next section on how to work around this. - -### [iOS (all supported versions) and macOS 10.14+] Requesting notification permissions - -The constructor for the `DarwinInitializationSettings` class has three named parameters (`requestSoundPermission`, `requestBadgePermission` and `requestAlertPermission`) that controls which permissions are being requested. If you want to request permissions at a later point in your application on iOS, set all of the above to false when initialising the plugin. - -```dart - FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); - const AndroidInitializationSettings initializationSettingsAndroid = - AndroidInitializationSettings('app_icon'); - final DarwinInitializationSettings initializationSettingsDarwin = - DarwinInitializationSettings( - requestSoundPermission: false, - requestBadgePermission: false, - requestAlertPermission: false, - ); - final MacOSInitializationSettings initializationSettingsMacOS = - MacOSInitializationSettings( - requestAlertPermission: false, - requestBadgePermission: false, - requestSoundPermission: false); - final LinuxInitializationSettings initializationSettingsLinux = - LinuxInitializationSettings( - defaultActionName: 'Open notification'); - final InitializationSettings initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, - iOS: initializationSettingsDarwin, - macOS: initializationSettingsDarwin, - linux: initializationSettingsLinux); - await flutterLocalNotificationsPlugin.initialize(initializationSettings, - onDidReceiveNotificationResponse: onDidReceiveNotificationResponse); -``` - -Then call the `requestPermissions` method with desired permissions at the appropriate point in your application - -For iOS: - -```dart -final bool result = await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - IOSFlutterLocalNotificationsPlugin>() - ?.requestPermissions( - alert: true, - badge: true, - sound: true, - ); -``` - -For macOS: - -```dart -final bool result = await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - MacOSFlutterLocalNotificationsPlugin>() - ?.requestPermissions( - alert: true, - badge: true, - sound: true, - ); -``` - -Here the call to `flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation()` returns the iOS implementation of the plugin that contains APIs specific to iOS if the application is running on iOS. Similarly, the macOS implementation is returned by calling `flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation()`. The `?.` operator is used as the result will be null when run on other platforms. Developers may alternatively choose to guard this call by checking the platform their application is running on. -### Displaying a notification - -```dart -const AndroidNotificationDetails androidNotificationDetails = - AndroidNotificationDetails('your channel id', 'your channel name', - channelDescription: 'your channel description', - importance: Importance.max, - priority: Priority.high, - ticker: 'ticker'); -const NotificationDetails notificationDetails = - NotificationDetails(android: androidNotificationDetails); -await flutterLocalNotificationsPlugin.show( - 0, 'plain title', 'plain body', notificationDetails, - payload: 'item x'); -``` - -Here, the first argument is the id of notification and is common to all methods that would result in a notification being shown. This is typically set a unique value per notification as using the same id multiple times would result in a notification being updated/overwritten. - -The details specific to the Android platform are also specified. This includes the channel details that is required for Android 8.0+. Whilst not shown, it's possible to specify details for iOS and macOS as well using the optional `iOS` and `macOS` named parameters if needed. The payload has been specified ('item x'), that will passed back through your application when the user has tapped on a notification. Note that for Android devices that notifications will only in appear in the tray and won't appear as a toast aka heads-up notification unless things like the priority/importance has been set appropriately. Refer to the Android docs (https://developer.android.com/guide/topics/ui/notifiers/notifications.html#Heads-up) for additional information. The "ticker" text is passed here is optional and specific to Android. This allows for text to be shown in the status bar on older versions of Android when the notification is shown. - -### Scheduling a notification - -Starting in version 2.0 of the plugin, scheduling notifications now requires developers to specify a date and time relative to a specific time zone. This is to solve issues with daylight saving time that existed in the `schedule` method that is now deprecated. A new `zonedSchedule` method is provided that expects an instance `TZDateTime` class provided by the [`timezone`](https://pub.dev/packages/timezone) package. Even though the `timezone` package is be a transitive dependency via this plugin, it is recommended based on [this lint rule](https://dart-lang.github.io/linter/lints/depend_on_referenced_packages.html) that you also add the `timezone` package as a direct dependency. - -Once the depdendency as been added, usage of the `timezone` package requires initialisation that is covered in the package's readme. For convenience the following are code snippets used by the example app. - -Import the `timezone` package - -```dart -import 'package:timezone/data/latest_all.dart' as tz; -import 'package:timezone/timezone.dart' as tz; -``` - -Initialise the time zone database - -```dart -tz.initializeTimeZones(); -``` - -Once the time zone database has been initialised, developers may optionally want to set a default local location/time zone - -```dart -tz.setLocalLocation(tz.getLocation(timeZoneName)); -``` - -The `timezone` package doesn't provide a way to obtain the current time zone on the device so developers will need to use [platform channels](https://flutter.dev/docs/development/platform-integration/platform-channels) or use other packages that may be able to provide the information. [`flutter_timezone`](https://pub.dev/packages/flutter_timezone) is the current version of the original `flutter_native_timezone` plugin used in the example app. - -Assuming the local location has been set, the `zonedSchedule` method can then be called in a manner similar to the following code - -```dart -await flutterLocalNotificationsPlugin.zonedSchedule( - 0, - 'scheduled title', - 'scheduled body', - tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)), - const NotificationDetails( - android: AndroidNotificationDetails( - 'your channel id', 'your channel name', - channelDescription: 'your channel description')), - androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle); -``` - -On Android, the `androidScheduleMode` is used to determine the precision on when the notification would be delivered. In this example, it's been specified that it should appear at the exact time even when the device has entered a low-powered idle mode. Note that this requires that the exact alarm permission has been granted. If it's been revoked then the plugin will log an error message. Note that if the notification was scheduled to be recurring one but the permission had been revoked then it will no be scheduled as well. In either case, this is where developers may choose to schedule inexact notifications instead via the `androidScheduleMode` parameter. - -There is an optional `matchDateTimeComponents` parameter that can be used to schedule a notification to appear on a daily or weekly basis by telling the plugin to match on the time or a combination of day of the week and time respectively. - -If you are trying to update your code so it doesn't use the deprecated methods for showing daily or weekly notifications that occur on a specific day of the week then you'll need to perform calculations that would determine the next instance of a date that meets the conditions for your application. See the example application that shows one of the ways that can be done e.g. how schedule a weekly notification to occur on Monday 10:00AM. - -### Periodically show a notification with a specified interval - -**Note** This is not supported on Windows - -```dart -const AndroidNotificationDetails androidNotificationDetails = - AndroidNotificationDetails( - 'repeating channel id', 'repeating channel name', - channelDescription: 'repeating description'); -const NotificationDetails notificationDetails = - NotificationDetails(android: androidNotificationDetails); -await flutterLocalNotificationsPlugin.periodicallyShow(0, 'repeating title', - 'repeating body', RepeatInterval.everyMinute, notificationDetails, - androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle); -``` - -### Retrieving pending notification requests - -```dart -final List pendingNotificationRequests = - await flutterLocalNotificationsPlugin.pendingNotificationRequests(); -``` - -### Retrieving active notifications - -**Note** On Windows, your app must be packaged as an MSIX to do this. See the limitations section. - -```dart -final List activeNotifications = - await flutterLocalNotificationsPlugin.getActiveNotifications(); -``` - -**Note**: The API only works for the following operating systems and versions -- Android 6.0 or newer -- iOS 10.0 or newer -- macOS 10.14 or newer - -### Grouping notifications - -#### iOS - -For iOS, you can specify `threadIdentifier` in `DarwinNotificationDetails`. Notifications with the same `threadIdentifier` will get grouped together automatically. - -```dart -const DarwinNotificationDetails iOSPlatformChannelSpecifics = - DarwinNotificationDetails(threadIdentifier: 'thread_id'); -``` - -#### Android - -This is a "translation" of the sample available at https://developer.android.com/training/notify-user/group.html - -```dart -const String groupKey = 'com.android.example.WORK_EMAIL'; -const String groupChannelId = 'grouped channel id'; -const String groupChannelName = 'grouped channel name'; -const String groupChannelDescription = 'grouped channel description'; -// example based on https://developer.android.com/training/notify-user/group.html -const AndroidNotificationDetails firstNotificationAndroidSpecifics = - AndroidNotificationDetails(groupChannelId, groupChannelName, - channelDescription: groupChannelDescription, - importance: Importance.max, - priority: Priority.high, - groupKey: groupKey); -const NotificationDetails firstNotificationPlatformSpecifics = - NotificationDetails(android: firstNotificationAndroidSpecifics); -await flutterLocalNotificationsPlugin.show(1, 'Alex Faarborg', - 'You will not believe...', firstNotificationPlatformSpecifics); -const AndroidNotificationDetails secondNotificationAndroidSpecifics = - AndroidNotificationDetails(groupChannelId, groupChannelName, - channelDescription: groupChannelDescription, - importance: Importance.max, - priority: Priority.high, - groupKey: groupKey); -const NotificationDetails secondNotificationPlatformSpecifics = - NotificationDetails(android: secondNotificationAndroidSpecifics); -await flutterLocalNotificationsPlugin.show( - 2, - 'Jeff Chang', - 'Please join us to celebrate the...', - secondNotificationPlatformSpecifics); - -// Create the summary notification to support older devices that pre-date -/// Android 7.0 (API level 24). -/// -/// Recommended to create this regardless as the behaviour may vary as -/// mentioned in https://developer.android.com/training/notify-user/group -const List lines = [ - 'Alex Faarborg Check this out', - 'Jeff Chang Launch Party' -]; -const InboxStyleInformation inboxStyleInformation = InboxStyleInformation( - lines, - contentTitle: '2 messages', - summaryText: 'janedoe@example.com'); -const AndroidNotificationDetails androidNotificationDetails = - AndroidNotificationDetails(groupChannelId, groupChannelName, - channelDescription: groupChannelDescription, - styleInformation: inboxStyleInformation, - groupKey: groupKey, - setAsGroupSummary: true); -const NotificationDetails notificationDetails = - NotificationDetails(android: androidNotificationDetails); -await flutterLocalNotificationsPlugin.show( - 3, 'Attention', 'Two messages', notificationDetails); -``` +// 2. Register the handler, along with any platform-specific settings +final androidSettings = AndroidInitializationSettings("app_icon"); +final plugin = FlutterLocalNotificationsPlugin(); +await plugin.initialize( + InitializationSettings(android: androidSettings), + onDidReceiveNotificationResponse: onNotificationTapped, +); -### Cancelling/deleting a notification +final androidPlugin = plugin + .resolvePlatformSpecificImplementation + (); -**Note** On Windows, your app must be packaged as an MSIX to do this. See the limitations section. +// 3. Request permission (after a user interaction) +await androidPlugin?.requestNotificationsPermission(); -```dart -// cancel the notification with id value of zero -await flutterLocalNotificationsPlugin.cancel(0); +// 4. Create any notification channels that will be needed +final messagesChannel = AndroidNotificationChannel("chat", "Chat Messages"); +await androidPlugin?.createNotificationChannel(messagesChannel); ``` -### Cancelling/deleting all notifications +### Show a notification ```dart -await flutterLocalNotificationsPlugin.cancelAll(); -``` - -### Getting details on if the app was launched via a notification created by this plugin +// Define your platform-specific details +final details = AndroidNotificationDetails( + "chat", "Chat Messages", + actions: [ + AndroidNotificationAction("_", "Ignore"), + AndroidNotificationAction( + "reply", "Reply", + inputs: [ + AndroidNotificationActionInput(label: "Enter a message..."), + ], + ) + ] +); -```dart -final NotificationAppLaunchDetails? notificationAppLaunchDetails = - await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); +// Show the notification +await plugin.show( + 0, "Alice sent you a message!", "Hey, are you available?", + NotificationDetails(android: details), + payload: "message_ID_123", +); ``` -### [iOS only] Periodic notifications showing up after reinstallation +### Gallery -If you have set notifications to be shown periodically on older iOS versions (< 10) and the application was uninstalled without cancelling all alarms, then the next time it's installed you may see the "old" notifications being fired. If this is not the desired behaviour then you can add code similar to the following to the `didFinishLaunchingWithOptions` method of your `AppDelegate` class. +| | | | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| | | | +## Supported Features -Objective-C: +There are *many* features supported by all the platforms, but here are the most common ones: -```objc -if(![[NSUserDefaults standardUserDefaults]objectForKey:@"Notification"]){ - [[UIApplication sharedApplication] cancelAllLocalNotifications]; - [[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"Notification"]; -} -``` +| Feature | Android | iOS / MacOS | Linux* | Windows | +| --------------------------------- | ------------------ | ------------------ | -------------------- | -------------------- | +| Show notifications | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Handle notification response | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Request permissions | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign:** | :heavy_minus_sign:** | +| Schedule notifications | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | +| Repeating notifications | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | +| Notification actions (buttons) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Notification actions (text field) | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | +| Stylized notifications | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: | +| Notification Grouping | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | +| Custom images and sounds | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Bypass Do Not Disturb | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -Swift: +\* Linux features are dependent on the device's [notification server capabilities](./docs/linux-usage.md#server-capabilities) -```swift -if(!UserDefaults.standard.bool(forKey: "Notification")) { - UIApplication.shared.cancelAllLocalNotifications() - UserDefaults.standard.set(true, forKey: "Notification") -} -``` +\*\* Permissions not required for Linux and Windows devices -## 📈 Testing +## Need help? -As the plugin class is not static, it is possible to mock and verify its behaviour when writing tests as part of your application. -Check the source code for a sample test suite that has been kindly implemented (_test/flutter_local_notifications_test.dart_) that demonstrates how this can be done. +There is a fully-setup [example app](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications/example) with almost every feature this plugin supports. If you're having issues or something is unclear, please check the app for its configuration and usage. Remember to read the [General Usage Guide](./docs/usage.md), as well as your specific platform's setup and usage guides, linked in the table above. If you have a question on the API, try searching the [API Reference](https://pub.dev/documentation/flutter_local_notifications/latest/). -If you decide to use the plugin class directly as part of your tests, the methods will be mostly no-op and methods that return data will return default values. +If you're still having issues, feel free to file an issue on GitHub, and be sure to share a link to your repository so that we can reproduce the issue. **Just submitting code or an error output isn't enough**, since there are so many complex setup steps involved. If you cannot share your app's code, make a [Minimally Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) and share that with us. If you cannot reproduce the error, it is likely that you're missing a step or have a conflicting plugin causing the issues -Part of this is because the plugin detects if you're running on a supported plugin to determine which platform implementation of the plugin should be used. If the platform isn't supported, it will default to the aforementioned behaviour to reduce friction when writing tests. If this not desired then consider using mocks. +## Acknowledgements -If a platform-specific implementation of the plugin is required for your tests, use the [debugDefaultTargetPlatformOverride](https://api.flutter.dev/flutter/foundation/debugDefaultTargetPlatformOverride.html) property provided by the Flutter framework. +* [Javier Lecuona](https://github.com/javiercbk) for adding the ability to have notifications shown daily +* [Jeff Scaturro](https://github.com/JeffScaturro) for fixing the iOS issue around repeating notifications and migrating the plugin to AndroidX +* [Ian Cavanaugh](https://github.com/icavanaugh95) for helping create a sample to reproduce the problem reported in [issue #88](https://github.com/MaikuB/flutter_local_notifications/issues/88) +* [Zhang Jing](https://github.com/byrdkm17) for adding 'ticker' support for Android notifications +* [Kenneth](https://github.com/kennethnym), [lightrabbit](https://github.com/lightrabbit), and [Levi Lesches](https://github.com/Levi-Lesches) for adding Windows support and overhauling the documentation +* ...and everyone else for their contributions. They are greatly appreciated diff --git a/flutter_local_notifications/docs/android-setup.md b/flutter_local_notifications/docs/android-setup.md new file mode 100644 index 000000000..b62cb1ed0 --- /dev/null +++ b/flutter_local_notifications/docs/android-setup.md @@ -0,0 +1,297 @@ +# Android Setup + +> [!Important] +> Before proceeding, please make sure you are using the latest version of the plugin, since some versions require changes to the setup process. + +While this plugin handles some of the setup, other settings are required on a project basis and therefore must be applied within your project before notifications will work. + +If you have already made modifications to these files, please be extra careful and pay attention to context to avoid losing your changes. As always, it is recommended to version control your application to avoid losing changes. + +If something isn't clear, keep in mind the [example app](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications/example) has all of this setup done already, so you can use it as a reference. + +This guide will only handle the setup that comes before compiling your application. For details on what this plugin can do and how to use it, see the [Android Usage Guide](android-usage.md). + +## Gradle and Kotlin + +Gradle is Android's build system, and controls important options during compilation. There are two similarly named files, The **Project build file** (`android/build.gradle`), and the **Module build file** (`android/app/build.gradle`). Pay close attention to which one is being referred to in the following sections before making modifications. + +Gradle files also come in two syntaxes: +1. Groovy (legacy): `build.gradle` +2. Kotlin (recommended): `build.gradle.kts` + +It is recommended to switch to Kotlin, and new apps created with `flutter create` come with the Kotlin style by default. At the time of writing, however, there are [known performance issues](https://github.com/gradle/gradle/issues/15886) with Kotlin, and switching to Groovy will take time, so these docs will have both styles for reference. If you're looking to migrate, see [this guide](https://developer.android.com/build/migrate-to-kotlin-dsl) from Android and [this guide](https://docs.gradle.org/current/userguide/migrating_from_groovy_to_kotlin_dsl.html) from Gradle. + +### Java Desugaring + +This plugin relies on [desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) to take advantage of newer Java features on older versions of Android. Desugaring must be enabled in your _module_ build file, like this: + +
+Groovy - android/app/build.gradle + +```groovy +android { + defaultConfig { + multiDexEnabled true + } + + compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = "11" + } +} + +dependencies { + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' +} +``` + +
+ +
+Kotlin - android/app/build.gradle.kts + +```kotlin +android { + defaultConfig { + multiDexEnabled = true + } + + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } +} + +dependencies { + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") +} +``` + +
+ +> [!Warning] +> There was [a crash](https://github.com/flutter/flutter/issues/110658) that used to occur on devices running Android 12L and above. Flutter has since fixed the issue in 3.24.0. If you are using an earlier version of Flutter, you'll need to add it manually to your _module_ build file: + +
Groovy - android/app/build.gradle + +```groovy +dependencies { + implementation 'androidx.window:window:1.0.0' + implementation 'androidx.window:window-java:1.0.0' +} +``` + +
+ +
Kotlin - android/app/build.gradle.kts + +```kotlin +dependencies { + implementation("androidx.window:window:1.0.0") + implementation("androidx.window:window-java:1.0.0") +} +``` + +
+ +### Upgrading the Android Gradle Plugin + +This package uses the Android Gradle Plugin (AGP) version 8.6.0, so your application should use that version or higher. Make sure you are using the new declarative plugin syntax by following the guide [here](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply), making sure to use an AGP plugin version of `8.6.0` or higher. Your `android/settings.gradle` file should have this: + +
Groovy - settings.gradle + +```groovy +plugins { + id "com.android.application" version "8.6.0" apply false +} +``` + +
+ +
Kotlin - settings.gradle.kts + +```kotlin +plugins { + id("com.android.application") version "8.6.0" apply false +} +``` + +
+ +### Upgrading your minimum Android SDK + +This plugin requires Android SDK version 35 or higher. Make sure your _module_ build file sets `compileSdk` to 34 or higher: + +
Groovy - android/app/build.gradle + +```groovy +android { + compileSdk 35 +} +``` + +
+ +
Kotlin - android/app/build.gradle.kts + +```kotlin +android { + compileSdk = 35 +} +``` + +
+ +## AndroidManifest.xml Setup + +While Gradle is used to compile your app, [the Android Manifest](https://developer.android.com/guide/topics/manifest/manifest-intro) is used to install and run your app. In this context, the manifest is responsible for declaring what permissions will be needed by the app. The manifest is located at `android/src/main/AndroidManifest.xml`. + +This plugin has its own manifest that requires the [`POST_NOTIFICATIONS`](https://developer.android.com/reference/android/Manifest.permission#POST_NOTIFICATIONS) and [`VIBRATE`](https://developer.android.com/reference/android/Manifest.permission#VIBRATE) permissions, but apps that need more advanced functionality must add them as needed. Note that some permissions might require special consent from the user or subject your app to special review from the Play Store. + +### Scheduling notifications + +Scheduled notifications do not survive device reboots. Adding the following line inside the `` tag ensures that the plugin can re-schedule notifications as needed. + +```xml + +``` + +Next, add the following receiver inside the `` tag so that the plugin can show the notification when it is triggered. + +```xml + + + + + + + + + + + +``` + +### Scheduling with precision + +By default, Android will only schedule notifications with approximate precision to save power in idle mode. Exact timing differences are not given by the Android docs, but in general, you should not expect your notification to arrive within the exact minute you set it. + +> [!Warning] +> Scheduling exact alarms prevents the Android OS from being able to properly optimize the device's energy usage and idle time, and can lead to noticeably worse battery life for your users. Carefully consider whether you actually need these permissions and be mindful of users with lower-performing hardware. + +> [!Note] +> Some Android device manufacturers implement non-standard app-killing behavior to extend battery life more aggressively than what the Android docs suggest. This behavior, if it applies to your app, is at the OS level and cannot be prevented by this plugin. See [this site]( https://dontkillmyapp.com) for a rundown of such manufacturers and what you can do for your users. + +If you need that level of precision, [you'll need another permission](https://developer.android.com/about/versions/14/changes/schedule-exact-alarms). For example, calendar and alarm apps are encouraged to use these. Take a moment to consider your app's circumstances: + +- Exact scheduling is a core requirement for your app. In this case, you'll need the [`USE_EXACT_ALARM`](https://developer.android.com/reference/android/Manifest.permission#USE_EXACT_ALARM) permission, which won't require user consent in-app but may subject your app to more stringent app store reviews. +- Exact scheduling is a nice-to-have addition for your app that users can opt-out of. In this case, you'll want to use the [`SCHEDULE_EXACT_ALARM`](https://developer.android.com/reference/android/Manifest.permission#SCHEDULE_EXACT_ALARM) permission. This permission will need to be granted by the user using [`requestExactAlarmsPermission()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/requestExactAlarmsPermission.html) function in Dart. This permission can be revoked at any time by the user or system, so use [`canScheduleExactNotifications()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/canScheduleExactNotifications.html) to check at run-time if you still have this permission. + +In any case, add the appropriate permission to your manifest, under the top-level `` tag. + +```xml + + + +``` + +Do not request both permissions, choose only one. + +### Full-screen notifications + +Some apps may need to take over the screen to show their notifications, such as alarms or incoming calls. This requires some modifications to your manifestL + +```xml + + + + + + + + + + +``` + +The [Android docs](https://source.android.com/docs/core/permissions/fsi-limits) indicate that this permission will be automatically revoked for apps that do not provide calling or alarm functionality, so use this permission sparingly. To request the permission at runtime, use [`requestFullScreenIntentPermission()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/requestFullScreenIntentPermission.html) in your Dart code. A Dart function to check this permission at runtime [does not yet exist](https://github.com/MaikuB/flutter_local_notifications/issues/2478). + +### Using notification actions + +Android, like most other platforms, supports adding buttons and text inputs to your notifications. These inputs are called actions, and can be to execute an action more specific than just opening the app, like responding to a message or opening to a specific page. To add actions to your app, add the following inside your `` tag: + +```xml + + + +``` + +### Using foreground services + +A [foreground service](https://developer.android.com/develop/background-work/services/foreground-services) indicates ongoing work in the form of a notification. You can use [`AndroidNotificationDetails.ongoing`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidNotificationDetails/ongoing.html) to specify if the notification should be non-dismissible. Note that this plugin does not implement logic in Dart to actually perform the operations, but rather just shows the service notification itself. + +Android defines many different types of foreground services. These are just predefined categories and not limitations, but it can still be useful to let the system know what type of service you're running. Refer to [this page](https://developer.android.com/develop/background-work/services/fg-service-types) to find all the different types of services. + +At runtime, use [`startForegroundService()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/startForegroundService.html) and pass the appropriate service types, if any. The system will check if you have the necessary permissions for each service type and reject your request if not. The prerequisite permissions can be found at the link above. When you're done, use `stopForegroundService()`. + +Foreground services require additions to the manifest. First, you must request the `FOREGROUND_SERVICE` permission, along with special permissions for each service type. Then, you must register the plugin service as shown below, adding any service types you may need. The exact names for each service type can be found at the link above. + +```xml + + + + + + + + + + +``` + +### Bypassing Do Not Disturb + +If your app will create notifications that will bypass Do Not Disturb, you'll need to declare a special permission in your manifest: + +```xml + +``` + +You'll also need to request permission at runtime with [`requestNotificationPolicyAccess()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/requestNotificationPolicyAccess.html). See the Android usage guide for more details. + +## Code and asset shrinking + +Flutter enables [code shrinking](https://developer.android.com/build/shrink-code) to minimize the release size by default. This means code and assets that were not determined to be used will automatically be stripped from your compiled application in release mode. This can be fine for Java code, but icons and other assets may be removed as well. Whether you're using the app icon for notifications (the default) or a custom icon, this can affect your app and your notifications or icons may not show. + +Be sure to follow the instructions [here](https://developer.android.com/topic/performance/app-optimization/customize-which-resources-to-keep) to protect your application from these problems. Make sure to include any icons or sounds you bundle with your app. If you're using your app icon for notifications, be sure to include `@mipmap/ic_launcher` in your `keep.xml` file as well. + +> [!WARNING] +> +> Code shrinking can affect the [GSON](https://github.com/google/gson) package as well, a Java dependency used by this plugin. Version 19.0.0 of this package includes the necessary ProGuard rules to protect it, but if you're using an earlier version, you'll need to manually copy the contents of [this file](https://github.com/google/gson/blob/main/examples/android-proguard-example/proguard.cfg) to `android/app/proguard-rules.pro`. + +## Custom icons and sounds + +Notification icons should be added as a [drawable resource](https://developer.android.com/guide/topics/resources/drawable-resource), just like app icons. By default, the app's own icon is `@mipmap/ic_launcher`, and any such value can be passed directly to the `AndroidNotificationDetails()` constructor. For more details on creating custom notification icons, [see the docs](https://developer.android.com/studio/write/create-app-icons#create-notification). Custom notification sounds should be added to the `res/raw` directory. + +Notifications may also make use of [large icons](https://developer.android.com/develop/ui/views/notifications/expanded#image-style), such as album art or message attachments. When calling `show()`, pass a [`largeIcon`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidNotificationDetails/largeIcon.html) to the `AndroidNotificationDetails()` constructor. Use a `DrawableResourceAndroidBitmap` to indicate an image file in `/res/drawable`, or use `FilePathAndroidBitmap` to point to any file. + +> [!Warning] +> +> Since these assets are only referred to at runtime, Android Studio might decide these assets are "unused" and remove them from your app in release mode. Be sure to follow the instructions in the previous section to preserve them. diff --git a/flutter_local_notifications/docs/android-usage.md b/flutter_local_notifications/docs/android-usage.md new file mode 100644 index 000000000..c66217dd0 --- /dev/null +++ b/flutter_local_notifications/docs/android-usage.md @@ -0,0 +1,202 @@ +# Android Usage Guide + +> [!IMPORTANT] +> +> Make sure to read the [Android Setup Guide](./android-setup.md) and [General Usage Guide](./usage.md) first. + +This guide will explore all the features this plugin has available for apps running on Android. Some features can only be called on the Android plugin, not the general plugin. The rest of this guide will assume the following: + +```dart +final plugin = FlutterLocalNotificationsPlugin(); +final androidPlugin = plugin + .resolvePlatformSpecificImplementation + (); +``` + +While some of these methods will have their arguments, return types, and usage spelled out in detail, this document is meant to complement the [API reference](https://pub.dev/documentation/flutter_local_notifications/latest/index.html) on Pub. If you're looking for more details, nuances, or information about a function's signature, refer to the reference. Remember you can also check the [example app](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications/example) for a pretty thorough reference of what this plugin can do. + +> [!NOTE] +> +> Even though the plugin supports Android API Level 21 and above, this guide will assume you are working with at least API Level 26. See [this table](https://apilevels.com/) for more details. + +## Initialization + +Android only has one option to configure – the default app icon. The icon should be added as a [drawable resource](https://developer.android.com/guide/topics/resources/drawable-resource), saved in `android/app/src/main/res/drawable`, preferable as a PNG or WEBP file. Specify the name of the icon without the file extension. For example, for a resource saved as `res/drawable/app_icon.png`, your `initialize()` call should look like this: + +```dart +final settings = InitializationSettings( + android: AndroidInitializationSettings("app_icon"), +); +await plugin.initialize(settings); +``` + +## Notification channels + +Android groups notifications of a similar purpose into [notification channels](https://developer.android.com/develop/ui/views/notifications#ManageChannels). Separate from notification grouping, this is meant to allow users to customize how their notifications are shown, like "new message" or "upcoming deals". Using channels consistently will give users confidence and more options when changing settings, and will allow them to silence some notifications without missing on others. + +> [!IMPORTANT] +> +> Starting with Android 8.0 (API Level 26), notifications that don't have an associated notification channel will not show. You must create a notification channel and provide an appropriate `channelId` every time you show or schedule a notification, which means you must provide an `AndroidNotificationDetails`. + +### Creating a notification channel + +To create a notification channel, use [`createNotificationChannel`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/createNotificationChannel.html): + +```dart +final chatChannel = AndroidNotificationChannel( + "chat-messages", "Chat Messages", + description: "New messages in private chats", + importance: Importance.high, + // ... +); +await androidPlugin?.createNotificationChannel(channel); +``` + +> [!Note] +> Notification sounds, vibration patterns, and importance levels are configured on the notification channel as a whole, not on each notification. These settings are finalized when the first notification of that channel is shown and cannot be changed. Instead, direct users to their system settings to make changes. + +### Notification channel options + +Notification channels support the following options. These options may only be set when the channel is being created, and may not be changed later. If you need to, you can delete the channel and recreate it, or guide the user to change it themselves. You can call `getNotificationChannels()` to get a list of channels, but note that users may change these options at any time in the settings, so the options may not be the same as when you created them. + +- `description`: will be shown to the user in the settings app, but not in the notifications themselves +- `groupId`: which notification channel group this channel belongs to (see below) +- `importance`: how important notifications in this channel should be +- `bypassDnd`: whether notifications in this group should bypass Do Not Disturb mode (see below) +- `playSound` , `sound`, and `audioAttributesUsage`: what sound notifications in this group will play. To use custom sounds, see [this section](./android-setup.md#custom-icons-and-sounds) +- `enableVibration` and `vibrationPattern`: how and whether the device should vibrate +- `enableLights` and `ledColor`: whether the device's LED should glow and with what color +- `showBadge`: whether the app should show a badge with these notifications + +### Notification channel groups + +If you have a lot of channels, you can also choose to use notification channel groups to group similar channels. First, create the group with [`createNotificationChannelGroup()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/createNotificationChannelGroup.html), then include the `groupId` in all your channels: + +```dart +final chatsGroup = AndroidNotificationChannelGroup( + "all-chats", "All Chats", description: "All your chat messages", +); +final privateChats = AndroidNotificationChannel( + "private-chats", "Private Chats", groupId: "all-chats", +); +final publicChats = AndroidNotificationChannel( + "group-chats", "Group Chats", groupId: "all-chats", +); +await androidPlugin?.createNotificationChannelGroup(chatsGroup); +await androidPlugin?.createNotificationChannel(privateChats); +await androidPlugin?.createNotificationChannelGroup(publicChats); +``` + +> [!NOTE] +> +> Once you've added a channel to a group, you cannot change it. If necessary, first delete the channels and groups using `deleteNotificationChannel()` and `deleteNotificationChannelGroup()`, then re-create them the way you want them. + +## Permissions + +This next section will deal with requesting permissions. As noted in the general usage guide, requesting permissions is a very sensitive process in terms of user experience, and must be done only when necessary and only after informing the user of why you need the permissions. If the user rejects your request – which they might if they feel annoyed or don't understand why you need it – Android will stop showing your requests to the user and start blocking them automatically. If this happens, you will need to prompt the user to go to the settings themselves and grant your app permissions manually. + +### Requesting notification permissions + +You will need permissions to show notifications at all. Be sure to call `androidPlugin?.requestNotificationsPermission()` sometime after calling `initialize()`, but before calling any method that shows or schedules a notification. You can use `androidPlugin?.areNotificationsEnabled()` to check your permissions at runtime. + +### Bypassing Do Not Disturb + +To have a notification bypass Do Not Disturb: + +- Follow [this section](./android-setup.md#bypassing-do-not-disturb) of the Android Setup Guide +- After calling `plugin.initialize()`, call [`androidPlugin?.requestNotificationPolicyAccess()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/requestNotificationPolicyAccess.html) +- Create a notification channel with `bypassDnd: true` +- Show a notification with a `channelId` that corresponds to that channel +- You can use `androidPlugin?.hasNotificationPolicyAccess()` to check your permission at runtime + +> [!Important] +> +> Bypassing Do Not Disturb does not mean the device's volume will be increased or vibration will be enabled, just that the notification will appear on-screen. In an emergency, the user may have sound and vibration disabled and won't notice your notification. Use a different package to enable sound and vibration if you need it. + +### Showing full screen notifications + +Some notifications need the user's full and complete attention, like a finished timer, an ongoing alarm, or incoming call. These notifications can take over the screen entirely and show a custom UI. Since they can be very invasive, they also require special permissions. + +- Follow [this section](./android-setup.md#full-screen-notifications) of the Android Setup Guide +- After calling `plugin.initialize()`, call [`androidPlugin?.requestFullScreenIntentPermission()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/requestFullScreenIntentPermission.html) +- Create a notification channel with `importance: Importance.high` or higher + +- Create a notification with `AndroidNotificationDetails(fullScreenIntent: true)` +- There is not yet a function to determine your full-screen permission at runtime ([GitHub issue](https://github.com/MaikuB/flutter_local_notifications/issues/2478)) + +> [!NOTE] +> +> Starting with API Level 34 (Android 14), only applications that provide call or alarm functionality will receive this permission. If the user is actively using the device, they will receive a heads-up notification instead. + +If the system chooses to show a full-screen notification, your app will be launched and `onDidReceiveNotificationResponse` will fire, allowing your app to show a special UI based on the notification. + +### Scheduling with precision + +Using `zonedSchedule()` or `periodicallyShowWithDuration()` will cause your notification to appear at an inexact time by default. You can request more exact timing, but this comes with noticeably more battery strain and more permissions. + +- Follow [this section](./android-setup.md#scheduling-with-precision) of the Android Setup Guide +- If you chose `SCHEDULE_EXACT_ALARM`, call [`androidPlugin?.requestExactAlarmsPermission()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/requestExactAlarmsPermission.html) +- Choose an appropriate [`AndroidScheduleMode`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidScheduleMode.html) +- called `zonedSchedule()` or `periodicallyShow()` with the `scheduleMode` parameter +- You can use `androidPlugin?.canScheduleExactNotifications()` to check your permission at runtime + +## Showing Notifications + +> [!IMPORTANT] +> +> You **must** provide an `AndroidNotificationDetails` for all notifications and provide a `channelId` that you created previously. If you do not pass these details, or pass a channel that does not exist, your notifications will not be shown. + +### Presentation options + +Most notification presentation options are set on the notification channel instead of the notification itself (see above). See [`AndroidNotificationDetails`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidNotificationDetails-class.html) for all available fields. Notable options include: + +- `icon`: use a custom icon for this notification. To use custom icons, see [this section](./android-setup.md#custom-icons-and-sounds) of the Android Setup Guide. +- `showProgress`: show a loading bar +- `groupKey`: use the same value across multiple notifications to group them together +- `usesChronometer`: show a clock that ticks up or down. Useful for timers or media playback +- `color`: sets an accent color. If `colorized` is true, this will be the background color instead +- `styleInfomation`: allows you to choose a [custom style](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/DefaultStyleInformation-class.html) for the notification +- and many more + +### Notification actions + +Notifications can sometimes be used to accept user input and act on their behalf without necessarily opening the app. See [this section](./android-setup#using-notification-actions) of the Android Setup Guide, [this section](./usage.md#notification-actions) of the General Usage Guide for details, and [this section](./usage.md#the-initialize-function) on how to respond to action interactions. See [`AndroidNotificationAction`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidNotificationAction-class.html) for the full API, or the following examples: + +```dart +final actionsDetails = AndroidNotificationDetails( + "my-channel", "My Channel Name", + actions: [ + // A basic button + AndroidNotificationAction("button-id", "Button 1"), + // A text field + AndroidNotificationAction( + "text-field-id", "Press to open text box", + inputs: [ + AndroidNotificationActionInput(label: "Enter a message"), + ], + ), + // Multiple choice + AndroidNotificationAction( + "choice-id", "Choose your favorite fruit", + inputs: [ + AndroidNotificationActionInput( + choices: ["Apple", "Banana"], + allowFreeFormInput: false, + ), + ], + ), + ], +); +``` + +### Foreground Services + +A [foreground service](https://developer.android.com/develop/background-work/services/foreground-services) indicates ongoing work in the form of a notification. You can use [`AndroidNotificationDetails.ongoing`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidNotificationDetails/ongoing.html) to specify if the notification should be non-dismissible, but a foreground service does more than that, including requesting to the device that your application be given a higher priority, even in the background. Note that this plugin does not implement logic in Dart to actually perform the operations, but rather just shows the service notification itself. + +Android defines many different types of foreground services. These are just predefined categories and not limitations, but it can still be useful to let the system know what type of service you're running. Refer to [this page](https://developer.android.com/develop/background-work/services/fg-service-types) to find all the different types of services. + +To create a foreground service, follow [this section](./android-setup.md#using-foreground-services) in the Android Setup Guide, then call [`startForegroundService()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/startForegroundService.html) and pass the appropriate service types, if any. The system will check if you have the necessary permissions for each service type and reject your request if not. The prerequisite permissions can be found at the link above. When you're done, use `stopForegroundService()`. + +> [!WARNING] +> +> The `startType` parameter must be set to `startNotSticky`. This means that your service will not be restarted if your app is killed. This is okay because the foreground service itself – on the Java side – does not contain any important logic, as that is carried out on the Dart side only. If you want to be able to perform important background work that is restarted when necessary, consider using a plugin like [`package:flutter_foreground_service`](https://pub.dev/packages/flutter_foreground_service) diff --git a/flutter_local_notifications/docs/darwin-usage.md b/flutter_local_notifications/docs/darwin-usage.md new file mode 100644 index 000000000..0dc4e9235 --- /dev/null +++ b/flutter_local_notifications/docs/darwin-usage.md @@ -0,0 +1,160 @@ +# Darwin Usage Guide + +> [!Important] +> +> Make sure to read the [iOS Setup Guide](./ios-setup.md) and [General Usage Guide](./usage.md) first. + +This guide will explore all the features this plugin has available for apps running on Darwin platforms – iOS and MacOS. Some features can only be called on the Darwin plugins, not the general plugin. See the general usage guide for how to get that at runtime. The rest of this guide will assume the following. + +```dart +final plugin = FlutterLocalNotificationsPlugin(); +final iOSPlugin = plugin + .resolvePlatformSpecificImplementation + (); +final macOSPlugin = plugin + .resolvePlatformSpecificImplementation + (); +final darwinPlugin = iOSPlugin; // or macOSPlugin +``` + +Both plugins support the same features, but you will still need to call the same methods on both plugins if your application will run on both platforms. + +While some of these methods will have their arguments, return types, and usage spelled out in detail, this document is meant to complement the [API reference](https://pub.dev/documentation/flutter_local_notifications/latest/index.html) on Pub. If you're looking for more details, nuances, or information about a function's signature, refer to the reference. Remember you can also check the [example app](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications/example) for a pretty thorough reference of what this plugin can do. + +> [!Note] +> +> While this plugin supports MacOS 10.13 and iOS 12, this guide will assume you are targeting iOS 14 and MacOS 11 or higher, which both released in 2020. + +## Initialization + +### Foreground options + +When the app is in the foreground, the device will, by default, not show the notification. To override this behavior, customize `DarwinInitializationSettings` (all of these default to `true`) + +- `defaultPresentBadge`: gives the app icon a badge +- `defaultPresentBanner`: shows a notification banner on-screen +- `defaultPresentList`: adds the notification to the Notification Center +- `defaultPresentSound`: plays a notification sound + +### Notification actions + +Notifications can sometimes be used to accept user input and act on their behalf without necessarily opening the app. See [this section](./ios-setup#handling-actions) of the iOS Setup Guide, [this section](./usage.md#notification-actions) of the General Usage Guide, and [this section](./usage.md#the-initialize-function) on how to respond to action interactions. + +On Darwin, you must declare your actions in `DarwinInitializationSettings` with the `notificationCategories` field, which takes a list of [`DarwinNotificationCategory`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/DarwinNotificationCategory-class.html) objects, each with their own list of [`DarwinNotificationAction`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/DarwinNotificationAction-class.html)s. Categories are defined for similar types of notifications, like new emails, interesting sales, and more. Each category will have the same set of actions. Here are some examples: + +```dart +// Set up the actions +final hotNewDeal = DarwinNotificationCategory( + "new-promo", + actions: [ + DarwinNotificationAction( + "purchase", "Buy this!", + options: {DarwinNotificationActionOption.foreground}, + ), + ], +), +final newMessage = DarwinNotificationCategory( + "new-message", + actions: [ + DarwinNotificationAction.text( + "reply", "Reply", buttonTitle: "Send a reply", + placeholder: "Enter a message...", + options: {DarwinNotificationActionOption.authenticationRequired}, + ), + ], +); + +// Set up a handler to handle tapped notifications +void onNotificationPressed(NotificationResponse response) { + switch (response.actionId) { // which action was pressed + "new-promo": goToPage("/deals/${response.payload}"); + "new-message": respond(response.payload, response.input); + } +} + +// Initialize the categories +final darwinSettings = DarwinInitializationSettings( + notificationCategories: [hotNewDeal, newMessage], +); + +await plugin.initialize( + InitializationSettings(iOS: darwinSettings, macOS: darwinSettings), + onDidReceiveNotificationResponse: onNotificationPressed, +); + +// Show the notification +final dealDetails = DarwinNotificationDetails(categoryIdentifier: "new-promo"); +await plugin.show( + 1, "A Hot New Deal!", "Look what's on sale!", + payload: "deal_id_123", + NotificationDetails(iOS: dealDetails, macOS: dealDetails), +); +``` + +## Requesting permissions + +This next section will deal with requesting permissions. As noted in the general usage guide, requesting permissions is a very sensitive process in terms of user experience, and must be done only when necessary and only after informing the user of why you need the permissions. If the user rejects your request – which they might if they feel annoyed or don't understand why you need it – Android will stop showing your requests to the user and start blocking them automatically. If this happens, you will need to prompt the user to go to the settings themselves and grant your app permissions manually. + +> [!Important] +> +> By default, `DarwinInitializationSettings` defaults to `true` for `requestAlertPermission`, `requestBadgePermission`, `requestListPermission`, and `requestSoundPermission`. You should set those all to false explicitly and follow the guidance below instead ([GitHub issue](https://github.com/MaikuB/flutter_local_notifications/issues/2693)) + +### Notification Permissions + +You will need permissions to show notifications at all. Be sure to call [`requestPermissions()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/MacOSFlutterLocalNotificationsPlugin/requestPermissions.html) sometime after calling `initialize()`, but before calling any method that shows or schedules a notification. You can use [`checkPermissions()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/MacOSFlutterLocalNotificationsPlugin/checkPermissions.html) to check your permissions at runtime. + +> [!NOTE] +> +> Use this method instead of setting fields like `DarwinInitializationSettings.requestSoundPermission`. You want to wait until the very last minute to request permissions, preferably until the user has initiated an action themselves. If users feel like the notification request is helping them complete an action, they are more likely to grant it as opposed to when it feels spammy. See Apple's guidance [here](https://developer.apple.com/documentation/usernotifications/asking-permission-to-use-notifications#Explicitly-request-authorization-in-context). + +### Provisional Notifications + +Instead of asking for broad permissions right away, you can request [provisional permissions](https://developer.apple.com/documentation/usernotifications/asking-permission-to-use-notifications#Use-provisional-authorization-to-send-trial-notifications) instead. This will allow your app to send notifications quietly in a way that doesn't disturb the user – instead of a banner or sound, your notification will be sent straight to the Notification Center. These notifications will also come with a prompt to the user if they want to keep allowing more notifications or turn them off, which can either grant or deny your broader permissions automatically. + +```dart +// Either right away +final darwinSettings = DarwinInitializationSettings(requestProvisionalPermission: true); +// Or when you need it +await darwinPlugin?.requestPermissions(provisional: true); +``` + +### Bypassing Do Not Disturb + +If you have notifications that are important enough to need to bypass Do Not Disturb: + +- Request the [Critical Alerts](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.usernotifications.critical-alerts) entitlement from Apple (requires a special form) +- Include `critical: true` in your `requestPermissions()` call +- Send a notification with `InterruptionLevel.critical` + + enum lets you control how much your notification should be allowed to interrupt the user. If you plan to use `InterruptionLevel.timeSensitive`, you'll need to [enable](https://help.apple.com/xcode/mac/current/#/dev88ff319e7) the time-sensitive capability. See the video [here](https://developer.apple.com/videos/play/wwdc2021/10091/) for more details. If you plan to use `InterruptionLevel.critical`, you'll need to [get approval](https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/) from Apple. + +> [!Important] +> +> Bypassing Do Not Disturb does not mean the device's volume will be increased or vibration will be enabled, just that the notification will appear on-screen. In an emergency, the user may have sound and vibration disabled and won't notice your notification. Use a different package to enable sound and vibration if you need it. + +## Showing notifications + +Make sure you've read the [General Usage Guide](./usage.md#showing-notifications) for a full understanding of the different functions. + +### Presentation options + +The [`DarwinNotificationDetails`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/DarwinNotificationDetails-class.html) has a few options, including + +- `presentBadge`, `presentSound`, `presentBanner`, and `presentList`: same as above +- `sound`: plays a custom sound (`presentSound` must be true). See [the Apple docs](https://developer.apple.com/documentation/usernotifications/unnotificationsound?language=objc) +- `badgeNumber`: changes the app badge count +- `attachments`: a list of [images](https://developer.apple.com/documentation/usernotifications/unnotificationattachment?language=objc) to show with the notification +- `subtitle`: a secondary description (separate from the notification's body) +- `threadIdentifier`: all notifications with the same value will be grouped together +- `categoryIdentifier`: the identifier of a category, as described above +- `interruptionLevel`: how much the device should get the user's attention + +> [!Note] +> +> Using `InterruptionLevel.critical` requires bypassing Do Not Disturb – see above +> Using `InterruptionLevel.timeSensitive` requires enabling the capability in XCode + +### Limitations + +- `getNotificationAppLaunchDetails()` will always return null prior to MacOS 10.14 +- iOS will only schedule up to 64 notifications at a time diff --git a/flutter_local_notifications/docs/ios-setup.md b/flutter_local_notifications/docs/ios-setup.md new file mode 100644 index 000000000..77b00c856 --- /dev/null +++ b/flutter_local_notifications/docs/ios-setup.md @@ -0,0 +1,125 @@ +# iOS Setup + +> [!Important] +> Before proceeding, please make sure you are using the latest version of the plugin, since some versions require changes to the setup process. + +While this plugin handles some of the setup, other settings are required on a project basis and therefore must be applied within your project before notifications will work. + +If you have already made modifications to these files, please be extra careful and pay attention to context to avoid losing your changes. As always, it is recommended to version control your application to avoid losing changes. + +If something isn't clear, keep in mind the [example app](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications/example) has all of this setup done already, so you can use it as a reference. + +This guide will only handle the setup that comes before compiling your application. For details on what this plugin can do and how to use it, see the [Darwin Usage Guide](darwin-usage.md). + +## General setup + +To start, add the following lines to the `application` method. The exact code differs between languages. + +
+Objective-C - ios/Runner/AppDelegate.m + +```objc +if (@available(iOS 10.0, *)) { + [UNUserNotificationCenter currentNotificationCenter].delegate = (id) self; +} +``` + +
+ +
+Swift - ios/Runner/AppDelegate.swift + +```swift +if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate +} +``` + +
+ +## Handling actions + +Apps can define [actions](https://developer.apple.com/documentation/usernotifications/declaring-your-actionable-notification-types) in their notifications which allow users to interact with the notifications on a deeper level without needing to navigate through the app. + +A standard notification can be clicked, which opens the app in the standard way. A notification action, however, can get input from the user and pass it to a custom function, which can then open a specific page in the app or run a background task without opening the app at all. + +To enable actions in your app, you must hook Flutter into the launch process. Normally, Flutter will do this for you in regular apps, but since actions can open your app in a non-standard way, you must perform the following setup. + +
+Objective-C - ios/Runner/AppDelegate.m + +```objc +// Required to handle notification actions +#import + +void registerPlugins(NSObject* registry) { + [GeneratedPluginRegistrant registerWithRegistry:registry]; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + [FlutterLocalNotificationsPlugin setPluginRegistrantCallback:registerPlugins]; +} +``` + +
+ +
+Swift - ios/Runner/AppDelegate.swift + + +Swift (`ios/Runner/AppDelegate.swift`): + +```swift +// Required to handle notification actions +import flutter_local_notifications + +@UIApplicationMain +override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? +) -> Bool { + + // This is required to make any communication available in the action isolate. + FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { + (registry) in GeneratedPluginRegistrant.register(with: registry) + } + + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate + } + + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) +} +``` + +
+ +## Clearing notifications on reinstall + +On some devices (older than iOS 10), if the user uninstalls the app without cancelling their repeating notifications, the app may reschedule those notifications if it's reinstalled. To prevent this, add the following code to the `didFinishLaunchingWithOptions()` method of the `AppDelegate` class. + +
+Objective-C - ios/Runner/AppDelegate.m + +```objc +if(![[NSUserDefaults standardUserDefaults]objectForKey:@"Notification"]){ + [[UIApplication sharedApplication] cancelAllLocalNotifications]; + [[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"Notification"]; +} +``` + +
+ +
+Swift - ios/Runner/AppDelegate.swift + +```swift +if(!UserDefaults.standard.bool(forKey: "Notification")) { + UIApplication.shared.cancelAllLocalNotifications() + UserDefaults.standard.set(true, forKey: "Notification") +} +``` + +
diff --git a/flutter_local_notifications/docs/linux-usage.md b/flutter_local_notifications/docs/linux-usage.md new file mode 100644 index 000000000..732769fca --- /dev/null +++ b/flutter_local_notifications/docs/linux-usage.md @@ -0,0 +1,105 @@ +# Linux Usage Guide + +> [!Important] +> +> Make sure to read the [General Usage Guide](./usage.md) first. +> + +This guide will explore all the features this plugin has available for apps running on Linux. Some features can only be called on the Linux plugin, not the general plugin. The rest of this guide will assume the following: + +```dart +final plugin = FlutterLocalNotificationsPlugin(); +final linuxPlugin = plugin + .resolvePlatformSpecificImplementation + (); +``` + +While some of these methods will have their arguments, return types, and usage spelled out in detail, this document is meant to complement the [API reference](https://pub.dev/documentation/flutter_local_notifications/latest/index.html) on Pub. If you're looking for more details, nuances, or information about a function's signature, refer to the reference. Remember you can also check the [example app](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications/example) for a pretty thorough reference of what this plugin can do. + +## Initialization + +The `LinuxInitializationSettings` object takes a few parameters + +- `defaultActionName` (required): The default text to show for the available action +- `defaultIcon`: The default icon for all notifications (see below for icon types) +- `defaultSound`: The default sound for all notifications (see below for sound types) +- `defaultSuppressSound`: Asks the notification server to not play any sounds + +### Server capabilities + +On Linux, notifications are shown by sending D-Bus messages to a notifications server that implements the [Desktop Notifications Specification](https://specifications.freedesktop.org/notification-spec/). In order to stay server-agnostic, you can query the server ahead of time with [`linuxPlugin?.getCapabilities()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/LinuxFlutterLocalNotificationsPlugin/getCapabilities.html) to find out what features it supports. That function returns a list of [`LinuxServerCapabilities`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/LinuxServerCapabilities-class.html) that describe what kinds of features it supports. You can then decide to not use certain features if you know the server does not support them. Notable capabilities to watch out for include: + +- `actions`: Notification Actions (see below) +- `body`: Whether the `body` parameter to `show()` will be respected +- `persistence`: Whether to show the notification until the user dismisses it +- `sound`: Whether the server can play sounds + +## Showing notifications + +### Presentation options + +The [`LinuxNotificationDetails`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/LinuxNotificationDetails-class.html) class provides a lot of options, including: + +- `icon` and `sound`: see below +- `category` and `urgency`: pre-defined options that describe the type of message +- `resident` and `transient`: whether the notification should remain or not +- `location`: where to display the notification + +### Markup + +If the server supports the `bodyMarkup` capability, you may use the following HTML tags in your body: + +- `Bold text` +- `Italic text` +- `Underlined text` +- `Links` + +If the server supports the `bodyImages` capability, you may include images in your body as well: + +- `image description` + +### Notification Actions + +Notifications can sometimes be used to accept user input and act on their behalf without necessarily opening the app. See [this section](./usage.md#notification-actions) of the General Usage Guide, and [this section](./usage.md#the-initialize-function) on how to respond to action interactions. Here is an example of button actions: +```dart +void onNotifcationTapped(NotificationResponse response) { + switch (response.actionId) { + "delete-message": deleteMessage(id: response.payload); + "mark-as-read": markMessageAsRead(id: response.payload); + } +} + +final details = LinuxNotificationDetails( + actions: [ + LinuxNotificationAction(key: "delete-message", label: "Delete message"), + LinuxNotificationAction(label: "mark-as-read", label: "Mark as Read"), + ], +); +await plugin.show( + 2, "New message from Alice", "Hey, are you free?", + NotificationDetails(linux: details), + payload: "message_ID_123", +); +``` + +### Custom icons + +To use a custom icon, specify `LinuxInitializationSettings.defaultIcon` for every notification, or override on a per-notification basis with `LinuxNotificationDetails.icon`. Icons can be one of: + +- `AssetsLinuxIcon`, which take a Flutter asset +- `ByteDataLinuxIcon`, which takes a [`LinuxRawIconData`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/LinuxRawIconData-class.html) +- `ThemeLinuxIcon`, which takes one of [these](https://www.freedesktop.org/wiki/Specifications/icon-naming-spec/) pre-defined icon names +- `FilePathLinuxIcon`, which takes am image from a `file://` URI or absolute path + +### Custom sounds + +To use a custom sound, specify `LinuxInitializationSettings.defaultSound` for every notification, or override on a per-notification basis with `LinuxNotificationDetails.sound`. Sounds can be: + +- `AssetsLinuxSound`, which takes a Flutter asset +- `ThemeLinuxSound`, which takes one of [these](https://www.freedesktop.org/wiki/Specifications/sound-theme-spec/) pre-defined sound names + +## Limitations + +- Scheduling and repeating notifications are not supported on Linux. + +- All notifications are sent to the main `onDidReceiveNotificationResponse` handler, which runs on the main isolate of the running application and cannot be launched in the background if the application is not running. To respond to notification clicks after the application is terminated, [register your application as D-Bus activatable](https://wiki.gnome.org/HowDoI/DBusApplicationLaunching). This is not feasible to do in this plugin since plugins are loaded after the application, so `getNotificationAppLaunchDetails()` wouldn't be possible to implement correctly. \ No newline at end of file diff --git a/flutter_local_notifications/docs/usage.md b/flutter_local_notifications/docs/usage.md new file mode 100644 index 000000000..a3f1394d3 --- /dev/null +++ b/flutter_local_notifications/docs/usage.md @@ -0,0 +1,269 @@ +# General usage + +While each platform inherently supports different types of notifications and features, there is quite a lot of functionality that can be accessed on all platforms. This page describes how to work with the common features, and access platform-specific ones. + +While some of these methods will have their arguments, return types, and usage spelled out in detail, this document is meant to complement the [API reference](https://pub.dev/documentation/flutter_local_notifications/latest/index.html) on Pub. If you're looking for more details, nuances, or information about a function's signature, refer to the reference. Remember you can also check the [example app](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications/example) for a pretty thorough reference of what this plugin can do. + +## Concepts + +### The different plugin classes + +At its core, this plugin is described by the [`FlutterLocalNotificationsPlugin`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin-class.html) class. This abstract class declares all the methods that are more-or-less shared between the platforms, albeit with small differences that will be noted where applicable. + +Sometimes, though, you need to access features that are unique to a specific platform. For example, each platform has its own way of requesting permissions, and it may be important to do so on a per-platform basis. + +In such cases, the plugin offers a method, [`resolvePlatformSpecificImplementation()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/resolvePlatformSpecificImplementation.html), where `T` is the type of plugin for a given platform, eg, [`AndroidFlutterLocalNotificationsPlugin`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin-class.html). This method will return an instance of the Android plugin only on Android devices, and null otherwise. When combined with Dart's `?.` null-aware operator, you can safely and concisely program platform-specific functions. + +For example, to request [alarm permissions](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidFlutterLocalNotificationsPlugin/requestExactAlarmsPermission.html) on Android: + +```dart +final androidPlugin = plugin + .resolvePlatformSpecificImplementation + (); + +// On non-Android platforms, this will be null. +await androidPlugin?.requestExactAlarmsPermission(); +``` + +For a description of the features associated with each platform, see their respective usage guides. The rest of this document will focus primarily on features that are common to all platforms and accessible on the "main" plugin. + +### The different details classes + +The different platforms share a lot of functionality, but also differ wildly in how they implement it. To that end, this plugin offers a [`NotificationDetails`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/NotificationDetails-class.html) class that allows you to pass platform-specific notification details for every method. For example: + +```dart +final details = NotificationDetails( + android: AndroidNotificationDetails(silent: true), + iOS: DarwinNotificationDetails(presentSound: false), + macOS: DarwinNotificationDetails(presentSound: false), + linux: LinuxNotificationDetails(suppressSound: true), + windows: WindowsNotificationDetails(audio: WindowsNotificationAudio.silent()), +); + +await plugin.show(0, "This is a quiet notification", "Very quiet indeed", details); +``` + +In this example, all the platforms are doing the same thing in different ways. Of course, being silent is something all the platforms can do, but there are specific features that are available on some platforms and not others. This paradigm allows you to pick and choose what features you want. Note that "Darwin" refers to iOS and MacOS together, but the `NotificationDetails` class has separate parameters so you can still choose different behaviors for them. + +The platform-specific features will be described in more detail in their respective usage guides. + +## Initialization + +### Determining your timezone + +To schedule notifications, this plugin uses [`package:timezone`](https://pub.dev/packages/timezone) to ensure the notification comes at the right time for your users. The [`TZDateTime`](https://pub.dev/documentation/timezone/latest/timezone.standalone/TZDateTime-class.html) class has a few constructors to make this convenient, but many of them require a [`Location`](https://pub.dev/documentation/timezone/latest/timezone.standalone/Location-class.html) be passed. + +You'll need to add this before calling any scheduling-related methods. + +```dart +import 'package:flutter_timezone/flutter_timezone.dart'; +import 'package:timezone/data/latest_all.dart' as tz; +import 'package:timezone/timezone.dart' as tz; + +void initTimezones() { + if (kIsWeb || Platform.isLinux) return; + tz.initializeTimeZones(); + if (Platform.isWindows) return; + final timeZoneName = await FlutterTimezone.getLocalTimezone(); + tz.setLocalLocation(tz.getLocation(timeZoneName!)); +} +``` + +### The initialize function + +The [`initialize()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/initialize.html) method is used to set up the plugin: + +```dart +// 1. The initialization settings +final initSettings = InitializationSettings( + android: AndroidInitializationSettings("app_icon"), + iOS: DarwinInitializationSettings(requestSoundPermission: true), + macOS: DarwinInitializationSettings(requestSoundPermission: true), + linux: LinuxInitializationSettings(defaultActionName: "Open"), + windows: WindowsInitializationSettings( + appName: "My Application", + appUserModelId: "com.developerName.applicationName", + // Search online for GUID generators to make your own + guid: "d49b0314-ee7a-4626-bf79-97cdb8a991bb", + ), +); + +// 2. The foreground handler +void onNotificationTap(NotificationResponse response) { + // The app/UI app is in the foreground, and all state can be used + print("Got a notification with payload: ${response.payload}"); +} + +// 3. The background handler +@pragma("vm:entry_point") +void onNotificationTapBackground(NotificationResponse response) { + // The app/UI is suspended or terminated, and all state is gone + print("Got a notification with payload: ${response.payload}"); +} + +final plugin = FlutterLocalNotificationsPlugin(); +await plugin = await plugin.initialize( + initSettings, + onDidReceiveNotificationResponse: onNotificationTap, + onDidReceiveBackgroundNotificationResponse: onNotificationTapBackground, +); +``` + +#### 1. Initialization settings + +The [`InitializationSettings`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/InitializationSettings-class.html) is a wrapper around initialization settings for each platform, just like `NotificationDetails`. See each platform's respective usage guides for more details. You'll need to provide the initialization settings for all platforms your app is expected to run on, or the plugin will throw an error at runtime. + +#### 2. The foreground handler + +A notification that is tapped when the app is in the foreground and are supposed, or a notification with an action that specifically launches the app, will trigger the `onDidReceiveNotificationResponse` callback passed to `initialize()`. This callback is guaranteed to be called when your Dart code is running and your app is in the foreground, so it may do anything a normal function will do. A classic case is navigating to some page, like a message or chat thread. + +The callback provides a [`NotificationResponse`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/NotificationResponse-class.html), which provides all sorts of information about the notification that was tapped. This class has a few fields to determine what notification was pressed: + +- the `id` of the notification +- the `payload` attached to the original notification, if any +- the `actionId` representing what action, if any, was pressed +- the `input`, representing what the user entered into the text box, if any + - Windows will use the `data` field instead, since there may be multiple text fields + +These fields are all set when showing the notification. See below for more details. + +#### 3. The background handler + +When the application is suspended or terminated and a notification is tapped that does not launch the app, the `onDidReceiveBackgroundNotification` callback is triggered. This callback also provides a `NotificationResponse`, like the foreground handler, but the callback provided must be able to run in the background, in another isolate. + +Specifically, the function must be a top-level or static function annotated with + +```dart +@pragma('vm:entry-point') +``` + +which is needed to prevent Flutter from thinking the function is unused and stripping it from your release builds. + +This function can be run when your app is in the foreground or not, so it must be capable of running without using any plugins that may depend on Flutter initialization. Because the callback is run in another isolate, you cannot rely on any state or initialization performed in the main isolate, even from `main()`. Think of the background handler as an entirely new entrypoint for your app that must initialize minimal resources, perform just one task, then gracefully return. + +Be careful how much work you do in this state. Most devices will kill your background handler if it takes too long to complete or uses too many resources. As a rule of thumb, if you need to do a lot of work, prefer to save some data representing that work in a file or database, so you can do that work later when the user opens the app again, or send it to your server for more processing. + +### Checking if a notification launched the app + +When your app is terminated and a user taps a notification, they usually want to be taken to a specific part of your app. This is useful, for example, when viewing a new message, news article, or friend request. Use the [`getNotificationAppLaunchDetails()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/getNotificationAppLaunchDetails.html) function to get details on how the app was launched. + +The function returns an instance of [`NotificationAppLaunchDetails`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/NotificationAppLaunchDetails-class.html), which has two main components: + +- `didNotificationLaunchApp`, which is true if and only if the app was launched directly by a notification +- `notificationResponse`, containing the details of the notification that was tapped, if any (see above) + +For example, say you show a notification meant to lead the user to a specific page of your app. You may set the route of the desired page as the notification's `payload` property (see below). Then, to get the appropriate page: + +```dart +Future getInitialRoute() async { + final details = await plugin.getNotificationAppLaunchDetails(); + return details?.notificationResponse?.payload; +} +``` + +## Showing notifications + +### Requesting permission + +Android, iOS, MacOS, and the Web platforms will require the user to grant you permission before your app is allowed to show notifications. It's important to not just request notifications immediately, but to wait until the user presses a button to confirm they are ready to do so, and you should try to provide as much functionality as possible without requiring notification permissions. If you simply ask for permission on app startup, not only may your app [get rejected from the stores](https://developer.apple.com/app-store/review/guidelines/#4.5.4) or [upset your users](https://www.reddit.com/r/AskReddit/comments/1l8cxfz/what_are_the_most_annoying_notifications_your/), but if they deny your request, the device may not show your prompt again. Web browsers will also automatically block your request unless it's associated with a user action, like a button press. + +For more information, see the platform-specific usage guides. [This page](https://web.dev/articles/permissions-best-practices) also serves some good advice on permissions best practices. + +### Show a notification + +The [`show()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/show.html) function shows a notification immediately, and accepts five arguments: + +- the `id` of the notification, as an integer. This ID is used to uniquely identify the notification to the device. If you send a new notification with the same ID, it will be updated in-place, which is useful in cases like chat threads and loading notifications. +- an optional `title` of the notification, as a string. If null, platforms will usually show the name of the app instead. +- an optional `body` of the notification, as a string +- an optional `NotificationDetails`, as discussed above +- an optional notification `payload`, as a string. This will be available to your app, via `NotificationResponse.payload`, if the notification is pressed. Use this to provide enough context for your app to meaningfully respond, like taking your user to a specific page or sending a message. + +These same parameters will be present on most methods that show notifications. Other platform-specific functionality can be specified in the `NotificationDetails`. For an example, see [above](#the-different-details-classes) + +### Schedule a notification + +> [!NOTE] +> +> Not supported on Linux or the Web + +The [`zonedSchedule()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/zonedSchedule.html) method accepts the same five arguments as `show()`, but also accepts: + +- the `scheduledDate` to show the notification +- the `uiLocalNotificationDateInterpretation`, which describes how devices prior to iOS 10 should interpret the `scheduledDate` +- the `androidScheduleMode`, which determines how precisely the device should schedule the notification + - More precision requires special permissions -- see the Android setup and usage guides + +- an optional `matchDateTimeComponents`, which tells the OS whether and how to periodically schedule this notification + +Note that the `scheduledDate` is not a regular `DateTime`, but rather a [`TZDateTime`](https://pub.dev/documentation/timezone/latest/timezone.standalone/TZDateTime-class.html) from `package:timezone` to ensure location data is incorporated into the notification delivery time. See above for how to set that up and get the user's current location. For example: + +```dart +await plugin.zonedSchedule( + 0, "Your event is now!", "This is a body", + tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)), + NotificationDetails(...), + androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, + payload: "goto-event_EVENT_ID", +); +``` + +By default, notifications shown with this function will not repeat unless `matchDateTimeComponents` is provided, in which case the [`DateTimeComponents`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/DateTimeComponents.html) will specify how to repeat. For example, passing `DateTimeComponents.dayOfWeekAndTime` will make the notification repeat once a week, during the given date time. Where possible, though, try to use [`periodicallyShowWithDuration()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/periodicallyShowWithDuration.html) (see below). + +### Periodically show a notification + +> [!Note] +> +> Not supported on Linux, Windows, or the Web + +To periodically show a notification, use [`periodicallyShowWithDuration()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/periodicallyShow.html). It takes the same arguments as `show()`, but also accepts a `repeatIntervalDuration` specifying how far apart these notifications should be and an `androidScheduleMode` as explained above. For example: + +```dart +await plugin.periodicallyShowWithDuration( + 0, "Have you moved today?", "Exercise is important!", + Duration(hours: 1), + NotificationDetails(...), + androidScheduleMode: AndroidScheduleMode.inexact, + payload: "yes-they-moved", +) +``` + +### Notification actions + +While many details differ between platforms, all platforms support the concept of notification actions. See your platform's usage guide for exact details, but the general idea is allowing a user to select an action within your notification itself, triggering a response from your application without requiring the user to open your app and select it themselves. In some platforms and circumstances, this action may be triggered entirely in the background, allowing your user to stay in their current app. + +For example, imagine a messaging app that just got a message from another user. Your notification may include a text action, allowing the user to send a response directly within the notification. Again, the exact details here differ by platform. + +Actions affect how your app handles responses. The foreground handler, background handler, and `getNotificationAppLaunchDetails()` method all provide you with a `NotificationResponse` object, which will contain a non-null `actionId` if an action was used, with a non-null `input` (or `data`) if a text field was used. For example, a notification can have a payload `new-message_CHAT_ID` and an `actionId` of `mark-read`. + +### Custom icons or sounds + +All platforms support showing unique icons or sounds as part of your notification. These can sometimes be files downloaded at runtime, or a file somewhere on the user's device, but otherwise may need to be bundled with your app directly in a platform-specific way. Refer to the platform-specific setup guides for more details. + +### Importance + +All platforms support some idea of an importance/priority/urgency. Use this wisely, and try to be as granular as possible so as not to annoy your users. Some platforms may require special permissions for higher priorities that can bypass Do Not Disturb modes, so check the platform usage guides for more details. + +## Managing notifications + +### Cancelling notifications + +To cancel a notification, call [`cancel()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/cancel.html) with the ID used to create the notification. To cancel all notifications, use [`cancelAll()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/cancelAll.html) instead, or [`cancelAllPendingNotifications()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/cancelAllPendingNotifications.html) (not supported on Linux and Web). + +> [!Note] +> Windows will only allow you to use `cancel()` if you have **package identity**. Without it, this function will always do nothing, but `cancelAll()` will still work. See the Windows setup guide for more details. + +### Checking current notifications + +To see notifications that are still being shown but have not been dismissed or cancelled, use [`getActiveNotifications()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/getActiveNotifications.html). This returns a list of [`ActiveNotification`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/ActiveNotification-class.html) that contains information about the notification, such as its ID, title, body, payload, and more. + +> [!Note] +> Windows will only allow you to check notifications by ID if you have **package identity**. Without it, this function will always return an empty list. See the Windows setup guide for more details. + +### Check scheduled notifications + +To check for notifications that were scheduled for the future and have yet to be shown, use [`pendingNotificationRequests()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/pendingNotificationRequests.html). This function returns a list of [`PendingNotificationRequest`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/PendingNotificationRequest/PendingNotificationRequest.html`) objects, which provide information about the notification's ID, title, body, and payload. This is not supported on Linux and the Web, and will instead return an empty list. + +## Testing + +This class is based on the [`FlutterLocalNotificationsPlugin`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin-class.html). If you need to write unit tests involving this plugin, just mock out that class (and any of the platform-specific plugin classes you may have used) for your tests. If a platform-specific implementation of the plugin is required for your tests, use the [debugDefaultTargetPlatformOverride](https://api.flutter.dev/flutter/foundation/debugDefaultTargetPlatformOverride.html) property provided by the Flutter framework. diff --git a/flutter_local_notifications/docs/windows-setup.md b/flutter_local_notifications/docs/windows-setup.md new file mode 100644 index 000000000..84178184c --- /dev/null +++ b/flutter_local_notifications/docs/windows-setup.md @@ -0,0 +1,38 @@ +# Windows Setup + +> [!Important] +> Before proceeding, please make sure you are using the latest version of the plugin, since some versions require changes to the setup process. + +While this plugin handles some of the setup, other settings are required on a project basis and therefore must be applied within your project before notifications will work. + +If you have already made modifications to these files, please be extra careful and pay attention to context to avoid losing your changes. As always, it is recommended to version control your application to avoid losing changes. + +If something isn't clear, keep in mind the [example app](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications/example) has all of this setup done already, so you can use it as a reference. + +This guide will only handle the setup that comes before compiling your application. For details on what this plugin can do and how to use it, see the [Windows Usage Guide](windows-usage.md). + +## MSIX packaging + +The [MSIX packaging format](https://learn.microsoft.com/en-us/windows/msix/overview) is a relatively new technique to package and distribute modern Windows apps. An MSIX installs an app onto the system as if it were downloaded from the Windows store (indeed, Windows store apps are packaged with MSIX). MSIX installers can also be used to update an existing application while preserving user data, making them really convenient. + +However, their relevance here goes beyond convenience: Windows [restricts](https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/modernize-packaged-apps) some APIs to apps that have "package identity", ie, have been installed using an MSIX installer. That restriction includes notifications. Specifically, the following methods behave differently without package identity: + +- non-packaged apps cannot see details about previously shown notifications +- [`getActiveNotifications()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/getActiveNotifications.html) will return an empty list +- [`cancel()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/FlutterLocalNotificationsPlugin/cancel.html) does nothing as it cannot find the notification with the specified ID. + +Other APIs will most likely work during debug mode and in exe releases, but it is still highly recommended to use MSIX packaging instead. + +### Setting up MSIX + +The [msix package](https://pub.dev/packages/msix) can help generate an MSIX installer for your application. See the instructions there for full details, but you'll need to be consistent with the Dart code for this plugin to work: + +| Setting Name | `pubspec.yaml` (`msix_config`) | `WindowsInitializationSettings` | +| ------------ | ------------------------------ | ------------------------------- | +| Display Name | `display_name` | `appName` | +| Package name | `identity_name` | `appUserModelId` | +| Unique ID | `toast_activator.clsid` | `guid` | + +The display name is set in the MSIX and cannot be changed in Dart. The GUID/CLSID, as the name implies, needs to be _globally unique_. Avoid using IDs from tutorials as other apps on the user's device may be using them as well. Instead, use [online GUID generators](https://guidgenerator.com) to generate a new, unique GUID, and use that for your MSIX and Dart options. + +For a full example, see the `msix_config` section of [the example app's `pubspec.yaml`](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/pubspec.yaml). diff --git a/flutter_local_notifications/docs/windows-usage.md b/flutter_local_notifications/docs/windows-usage.md new file mode 100644 index 000000000..a9ad5e23b --- /dev/null +++ b/flutter_local_notifications/docs/windows-usage.md @@ -0,0 +1,196 @@ +# Windows Usage Guide + +> [!Important] +> +> Make sure to read the [Windows Setup Guide](./windows-setup.md) and [General Usage Guide](./usage.md) first. +> +> Specifically, [this section](./windows-setup.md#msix-packaging) of the Windows Setup Guide describes the concept of **package identity**, without which your app will have some limitations. Setting up package identity is preferred. + +This guide will explore all the features this plugin has available for apps running on Windows. Some features can only be called on the Windows plugin, not the general plugin. The rest of this guide will assume the following: + +```dart +final plugin = FlutterLocalNotificationsPlugin(); +final windowsPlugin = plugin + .resolvePlatformSpecificImplementation + (); +``` + +While some of these methods will have their arguments, return types, and usage spelled out in detail, this document is meant to complement the [API reference](https://pub.dev/documentation/flutter_local_notifications/latest/index.html) on Pub. If you're looking for more details, nuances, or information about a function's signature, refer to the reference. Remember you can also check the [example app](https://github.com/MaikuB/flutter_local_notifications/tree/master/flutter_local_notifications/example) for a pretty thorough reference of what this plugin can do. + +## Initialization + +The `WindowsInitializationSettings` object has a few parameters that need to match the ones provided in your MSIX configuration. Refer to [this table](./windows-setup.md#setting-up-msix) in the setup guide for details. If you're not using an MSIX package, then these values can be whatever you want them to be, but they must still be valid and properly formatted. The following is a full example: + +```yaml +# pubspec.yaml +msix_config: + display_name: Flutter Local Notifications Example + identity_name: com.example.FlutterLocalNotificationsExample + msix_version: 1.0.0.0 + store: false + install_certificate: false + output_name: example + toast_activator: + clsid: "d49b0314-ee7a-4626-bf79-97cdb8a991bb" + arguments: "msix-args" + display_name: "Flutter Local Notifications Example" +``` + +```dart +final windowsSettings = WindowsInitializationSettings( + appName: "Flutter Local Notifications Example", + appUserModelId: "com.example.FlutterLocalNotificationsExample", + guid: "d49b0314-ee7a-4626-bf79-97cdb8a991bb", +); +await plugin.initialize(InitializationSettings(windows: windowsSettings); +``` + +> [!Note] +> +> Without package identity, your application will have the following limitations: +> +> - `getActiveNotifications()` will always return an empty list +> - `cancel()` will not work, only `cancelAll()` + +## Showing notifications + +### Presentation options + +The [`WindowsNotificationDetails`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/WindowsNotificationDetails-class.html) class supports a number of options, including: + +- `actions` and `inputs` – see below +- `images`: a list of [`WindowsImage`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/WindowsImage-class.html)s, either shown regularly or used to override the app icon +- `rows`: a list of [`WindowsRow`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/WindowsRow-class.html)s and columns that group images and text together +- `progressBars`: a list of [`WindowsProgressBar`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/WindowsProgressBar-class.html)s +- `header`: uses a custom ID that can be used to group multiple notifications together +- `audio`: a [`WindowsNotificationAudio`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/WindowsNotificationAudio-class.html) to play, or `WindowsNotificationAudio.silent()` +- `duration`: describes how long the notification should stick around for +- `scenario`: a preset [`WindowsNotificationScenario`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/WindowsNotificationScenario.html) that describes the notification and its style +- `timestamp`: can be used to back-date a notification +- `subtitle`: a third line of text, under the body + +The example app has a [separate file](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/lib/windows.dart) just for all the Windows features, so be sure to check that out! You can also check out the [App Notifications](https://learn.microsoft.com/en-us/windows/apps/develop/notifications/app-notifications/) section of the Windows docs, and especially [this page](https://learn.microsoft.com/en-us/windows/apps/develop/notifications/app-notifications/adaptive-interactive-toasts?tabs=appsdk). + +### Notification Actions + +Notifications can sometimes be used to accept user input and act on their behalf without necessarily opening the app. See [this section](./usage.md#notification-actions) of the General Usage Guide, and [this section](./usage.md#the-initialize-function) on how to respond to action interactions. Here is an example of a few different types of actions in a notification + +
+Expand to see a full example + +```dart +final markReadButton = WindowsAction(content: "Mark as Read", arguments: "mark-read"); + +final deleteButton = WindowsAction( + content: "Delete", + arguments: "delete-message", + buttonStyle: WindowsButtonStyle.critical, + tooltip: "Delete this message", +); + +final respondTextInput = WindowsTextInput( + id: "reply-message", + placeHoldContent: "Enter a message...", + title: "Reply", +); + +final presetSelection = WindowsSelection( + id: "reply-message", + title: "Respond with a preset message", + items: [ + WindowsSelection(id: "reply1", content: "Hello!"), + WindowsSelection(id: "reply2", content: "Please leave me alone..."), + ], +); + +final details = WindowsNotificationDetails( + actions: [markReadButton, deleteButton], + inputs: [respondTextInput, presetSelection], +); + +await plugin.show( + 1, "Alice sent you a message", "Hey! Are you ready?", + NotificationDetails(windows: details), + payload: "message_ID_123", +); + +void onNotificationTapped(NotificationResponse response) { + final messageId = response.payload; + switch (response.actionId) { + case "delete-message": deleteMessage(messageId); + case "mark-read": markRead(messageID, true); + case "reply-message": + // Inputs on Windows are in NotificationResponse.data + final reply = response.data["reply-message"]; + replyToMessage(messageId, reply); + } +} +``` + +
+ +### Custom images and sounds + +images can come from different sources: the web, MSIX bundles, files on the user's device, or Flutter assets. See the documentation for [`WindowsImage`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/WindowsImage-class.html) for complete details on when each time of source is usable. + +Sounds can either be one of the [`WindowsNotificationSound`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/WindowsNotificationSound.html) presets, or a Flutter asset using [`WindowsNotificationAudio.asset()`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/WindowsNotificationAudio/WindowsNotificationAudio.asset.html). When used in an MSIX package, this constructor will automatically convert the resource to an `ms-appx://` URI, and use a fallback preset sound in debug or non-packaged releases. + +### Custom XML + +Windows notifications are sent in the form of an XML schema, which the Windows SDK has to parse and verify. Using the `WindowsNotificationDetails` class in this library lets you skip that and build your notification with pure Dart code, but if you wanted to use raw XML for some unsupported or custom behavior, you can: + +- `windowsPlugin?.isValidXml()` will validate that the XML is indeed valid Windows XML +- `windowsPlugin?.showRawXml()` is equivalent to `plugin.show()`, but with raw XML instead of details +- `windowsPlugin?.zonedScheduleRawXml()` is equivalent to `zonedSchedule()` but with raw XML + +For more information, see the [Windows docs](https://learn.microsoft.com/en-us/uwp/schemas/tiles/toastschema/schema-root). To test your XML during development, use the [Windows Notifications Visualizer](https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/notifications-visualizer), or input it into the text field in the Windows example app. + +### Data bindings + +Any time you need to supply a text value, like `title`, `body`, `subtitle`, or even in raw XML, you can use a "binding", which is essentially a variable you can replace on the fly. For example, instead of + +```dart +await plugin.show(1, "Alice sent you a message", null, null); +``` + +You can use + +```dart +final bindings = {"sender": "Alice"}; +final details = WindowsNotificationDetails(bindings: bindings); +await plugin.show( + 1, "{sender} sent you a message!", null, NotificationDetails(windows: details), +); +``` + +You can then update the bindings at any time, and the notification will be updated in-place: + +```dart +await windowsPlugin?.updateBindings({"sender": "Bob"}); +``` + +You can also use this to update progress bars in real-time, though there is a special method for that: + +```dart +final progressBar = WindowsProgressBar( + id: "downloading-progress", + status: "Downloading...", + value: null, // null=indeterminate, ie no progress yet + label: "Downloading...", // note: no progress yet +); +final details = WindowsNotificationDetails(progressBars: [progressBar]); +await plugin.show(2, "Downloading songs", null, NotificationDetails(windows: details)); + +void updateDownloadProgress(int percentage) { + progressBar.value = percentage / 100; + progressBar.label = "Downloading: $percentage%"; + // Must use the same notification ID and progress bar + await windowsPlugin?.updateProgressBar(2, progressBar); +} +``` + +## Limitations + +- Without package identity, `getActiveNotifications()` will always return an empty list +- Without package identity, `cancel()` will not work, use `cancelAll()` or `cancelPendingNotifications()` +- Windows does not support repeating notifications. Trying to use `periodicallyShow()` or `periodicallyShowWithDuration()` will result in an `UnsupportedError`. diff --git a/images/android_notification.png b/images/android_notification.png index f720663da..0e9541584 100644 Binary files a/images/android_notification.png and b/images/android_notification.png differ diff --git a/images/ios_notification.png b/images/ios_notification.png index f072eadf7..a231848e5 100644 Binary files a/images/ios_notification.png and b/images/ios_notification.png differ