diff --git a/flutter_local_notifications/README.md b/flutter_local_notifications/README.md index b96305fb8..3f8423ac7 100644 --- a/flutter_local_notifications/README.md +++ b/flutter_local_notifications/README.md @@ -8,51 +8,6 @@ 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. -## Table of contents - -- **[📱 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) -- **[🔧 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)** - ## 📱 Supported platforms * **Android+**. Uses the [NotificationCompat APIs](https://developer.android.com/reference/androidx/core/app/NotificationCompat) so it can be run older Android devices @@ -66,17 +21,6 @@ Note: the plugin requires Flutter SDK 3.13 at a minimum. The list of support pla ## ✨ Features * 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 @@ -115,59 +59,24 @@ Note: the plugin requires Flutter SDK 3.13 at a minimum. The list of support pla * [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.](http://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. +This plugin provides a way to update the badge count of your notifications, but doesn't support APIs for directly setting the badge count without showing a notification, and may not work on all Android launchers. If you need this for your application, consider using the [`app_badge_plus`](https://pub.dev/packages/app_badge_plus) 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 | @@ -188,173 +97,8 @@ Due to some limitations on iOS with how it treats null values in dictionaries, a * [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 at `android/app/build.gradle`. Please see the link on desugaring for details but the main parts needed in this Gradle file would be - -```gradle -android { - defaultConfig { - multiDexEnabled true - } - - compileOptions { - // Flag to enable support for the new language APIs - coreLibraryDesugaringEnabled true - // Sets Java compatibility to Java 8 - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} - -dependencies { - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2' -} -``` - -Note that the plugin uses Android Gradle plugin (AGP) 7.3.1 to leverage this functionality so to err on the safe side, applications should aim to use the same version at a **minimum**. Using a higher version is also needed as at point, Android Studio bundled a newer version of the Java SDK that will only work with Gradle 7.3 or higher (see [here](https://docs.flutter.dev/release/breaking-changes/android-java-gradle-migration-guide) for more details). 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:7.3.1' - ... - } -``` - -If your app is using the new declarative Plugin DSL syntax, please refer to the Flutter documentation [here](https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply) where they document where the AGP version can be specified - -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: - -```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 34 at a minimum: - -```gradle -android { - compileSdk 34 - ... -} -``` - -### 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 - - ``` - -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. - -### Release build configuration - -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/master/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. - -⚠️ 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/studio/build/shrink-code#keep-resources). 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. If you fail to do this, 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)). - ## 🔧 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. @@ -366,67 +110,6 @@ For iOS 10+, use the presentation options to control the behaviour for when a no 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 @@ -461,71 +144,6 @@ final DarwinInitializationSettings initializationSettingsDarwin = DarwinInitiali 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 @@ -664,91 +282,6 @@ Here, the first argument is the id of notification and is common to all methods 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 @@ -819,51 +352,6 @@ await flutterLocalNotificationsPlugin.show( 3, 'Attention', 'Two messages', notificationDetails); ``` -### Cancelling/deleting a notification - -**Note** On Windows, your app must be packaged as an MSIX to do this. See the limitations section. - -```dart -// cancel the notification with id value of zero -await flutterLocalNotificationsPlugin.cancel(0); -``` - -### Cancelling/deleting all notifications - -```dart -await flutterLocalNotificationsPlugin.cancelAll(); -``` - -### Getting details on if the app was launched via a notification created by this plugin - -```dart -final NotificationAppLaunchDetails? notificationAppLaunchDetails = - await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); -``` - -### [iOS only] Periodic notifications showing up after reinstallation - -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. - - -Objective-C: - -```objc -if(![[NSUserDefaults standardUserDefaults]objectForKey:@"Notification"]){ - [[UIApplication sharedApplication] cancelAllLocalNotifications]; - [[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"Notification"]; -} -``` - -Swift: - -```swift -if(!UserDefaults.standard.bool(forKey: "Notification")) { - UIApplication.shared.cancelAllLocalNotifications() - UserDefaults.standard.set(true, forKey: "Notification") -} -``` - ## 📈 Testing As the plugin class is not static, it is possible to mock and verify its behaviour when writing tests as part of your application. diff --git a/flutter_local_notifications/docs/android-setup.md b/flutter_local_notifications/docs/android-setup.md new file mode 100644 index 000000000..95571767d --- /dev/null +++ b/flutter_local_notifications/docs/android-setup.md @@ -0,0 +1,200 @@ +# 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. + +## Gradle Setup + +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. + +### 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: + +```gradle +android { + defaultConfig { + multiDexEnabled true + } + + compileOptions { + coreLibraryDesugaringEnabled true + } +} + +dependencies { + // For AGP 7.4+ + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' + // For AGP 7.3 + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.3' + // For AGP 4.0 to 7.2 + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.9' +} +``` +For more details, see the link above. + +> [!Warning] +> There was [a crash](https://github.com/flutter/flutter/issues/110658) that used to occur on devices running Android 12L. Flutter has since fixed the issue in 3.24.0. If you are using an earlier version of Flutter, you'll need to add the following to your _module_ build file: +> +> ```gradle +> dependencies { +> implementation 'androidx.window:window:1.0.0' +> implementation 'androidx.window:window-java:1.0.0' +> } +> ``` + +### Upgrading the Android Gradle Plugin + +This package uses AGP 7.3.1, so your package 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 `7.3.1` or higher. Your `android/settings.gradle` file should have this: + +```gradle +plugins { + // Use 7.3.1 or higher + id "com.android.application" version "7.3.1" apply false +} +``` + +### Upgrading your minimum Android SDK + +This project requires Android SDK version 34 or higher. Make sure your _module_ build file sets `compileSdk` to 34 or higher: + +```gradle +android { + compileSdk 34 +} +``` + +## 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. + +> [!Caution] +> 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). 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. + +### Using 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 + + + + + + + + + + +``` + +## Custom notification 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. If you use a `DrawableResourceAndroidBitmap`, that indicates an image file in `/res/drawable`. Otherwise, use `FilePathAndroidBitmap` to point to any file. + +### Notification channels + +Android groups notifications of a similar purpose into [channels](https://developer.android.com/develop/ui/views/notifications#ManageChannels). Separate from "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. To put notifications in the same channel, simply use the same `channelId` in your calls to `show()`. + +> [!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. + +## Code and asset shrinking + +Flutter enables [code shrinking](https://developer.android.com/build/shrink-code) to minimize the release size, but this may lead to issues with [GSON](https://github.com/google/gson), a Java dependency used by this package. To fix this, 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`. If you use custom resources like icons or sounds, be sure to follow [these instructions](https://developer.android.com/build/shrink-code#keep-resources) to prevent them from getting deleted during the build process. diff --git a/flutter_local_notifications/docs/ios-setup.md b/flutter_local_notifications/docs/ios-setup.md new file mode 100644 index 000000000..10772c199 --- /dev/null +++ b/flutter_local_notifications/docs/ios-setup.md @@ -0,0 +1,97 @@ +# 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. + +## General setup + +To start, add the following lines to the `application` method. The exact code differs between languages. + +For Objective-C (`ios/Runner/AppDelegate.m`): + +```objc +if (@available(iOS 10.0, *)) { + [UNUserNotificationCenter currentNotificationCenter].delegate = (id) self; +} +``` + +For 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 +// 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) + } + + // ... + return super.application(application, didFinishLaunchingWithOptions: launchOptions) +} +``` + +## Clearing notifications on reinstall + +On some devices, 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/usage.md b/flutter_local_notifications/docs/usage.md new file mode 100644 index 000000000..13e5a4f6f --- /dev/null +++ b/flutter_local_notifications/docs/usage.md @@ -0,0 +1,175 @@ +# Cross-platform 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.dev. If you're looking for more details, nuances, or information about a function's signature, refer to the reference. + +## 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 +plugin.resolvePlatformSpecificImplementation() + ?.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. + +## 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 (!Platform.isAndroid && !Platform.isIOS && !Platform.isMacOS) return; + tz.initializeTimeZones(); + 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 takes three arguments: + +#### 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. See each platform's respective usage guides for more details. + +#### 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. + +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. Continue reading to learn more about what each field corresponds to for a given notification. + +#### The background handler + +When the application is 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. + +### Checking if a notification launched the app + +When a user taps a notification to launch the app, they usually wish to be taken to a specific part of your app, usually to perform a certain purpose. 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, and +- `notificationResponse`, containing the details of the notification that was tapped, if any. + +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 { + // On some devices, this will always return null + final details = await plugin.getNotificationAppLaunchDetails(); + return details?.notificationResponse?.payload; +} +``` + +## Showing notifications + +### Specifying platform-specific details + +Since each platform handles notifications differently, only a few parameters are declared on each method below. The rest are declared in platform-specific notification detail classes, like [`AndroidNotificationDetails`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/AndroidNotificationDetails-class.html). These classes can get quite large, so refer to each platform's usage guide and the API reference for more details. + +The [`NotificationDetails`](https://pub.dev/documentation/flutter_local_notifications/latest/flutter_local_notifications/NotificationDetails-class.html) class is a container class that allows details to be specified or omitted for any platform. You should pass this wrapper object to the functions below instead of platform details directly. + +### Notification actions + +While many details differ between platforms, many 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. 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. The response has a few fields that can be affected when a user interacts with a notification's action: + +- the `notificationResponseType` field will be `NotificationResponseType.selectedNotificationAction` +- the `actionId` field will be set to the ID of the action the user tapped +- the `input` field may be set to whatever the user inputted to the field. Check your platform's usage guide for details, but this usually refers to a text input, if any +- the `data` field may be set to the data returned by the platform. For example, Windows uses this instead of the `input` field + +### Show a notification immediately + +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. On some platforms, re-using this ID will result in the notification being silently updated. Check your platform's usage guide for more details. +- 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` + +These same parameters will be present on most methods that show notifications. Other platform-specific functionality can be specified in the `notificationDetails` class, as described above. + +### Schedule a notification + +**Not supported on Linux** + +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 +- 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. + +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, try to use `periodicallyShowWithDuration()`. The previous example can be rewritten as + +```dart +previouslyShowWithDuration( + // ..., + repeatIntervalDuration: const Duration(days: 7), +) +``` + +### Periodically show a notification + +**Not supported on Windows or Linux** + +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. + +## Existing 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. + +> [!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 notifications + +#### Check 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 future 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. diff --git a/flutter_local_notifications/docs/windows-setup.md b/flutter_local_notifications/docs/windows-setup.md new file mode 100644 index 000000000..46dae049a --- /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. + +## 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. + +## 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: + +- The `display_name` MSIX option should match your `WindowsInitializationSettings.appName` option +- The `identity_name` MSIX option must be identical to your `WindowsInitializationSettings.appUserModelId` option +- Your `msix_config` must also specify a `toast_activator` section with the following: + - a `clsid` option that exactly matches `WindowsInitializationSettings.guid` + - a `display_name` option that will be on each notification 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 example app's `pubspec.yaml`](https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/pubspec.yaml).