flutter_local_notifications 1.4.3

  • Readme
  • Changelog
  • Example
  • Installing
  • 99

flutter_local_notifications #

pub package Build Status

A cross platform plugin for displaying local notifications.

Supported Platforms #

  • Android API 16+ (4.1+, the minimum version supported by Flutter). Uses the NotificationCompat APIs so it can be run older Android devices
  • iOS 8.0+ (the minimum version supported by Flutter). Supports the old and new iOS notification APIs (the User Notifications Framework introduced in iOS 10 but will use the UILocalNotification APIs for devices predating iOS 10)

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 the foreground, background or terminated
  • Determine if an app was launched due to tapping on a notification
  • [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 (see 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
  • [iOS] Request notification permissions and customise the permissions being requested around displaying notifications
  • [iOS] Display notifications with attachments

Note that this plugin aims to provide abstractions for all platforms as opposed to having methods that only work on specific platforms. However, each method allows passing in "platform-specifics" that contains data that is specific for customising notifications on each platform. This approach means that some scenarios may not be covered by the plugin. Developers can either fork or maintain their code for showing notifications in these situations. Note that the plugin still under development so expect the API surface to change over time.

IMPORTANT NOTES:

  • Recurring notifications on Android use the Alarm Manager API. This is standard practice but does mean the delivery of the notifications/alarms are inexact and this is documented Android behaviour as per the previous link. Note that it's been reported that Samsung's implementation of Android has imposed a maximum of 500 alarms that can be scheduled via this API and exceptions can occur when going over the limit
  • iOS has a limit on how many pending notifications it allows. This is a limit imposed by iOS where it will only keep 64 notifications that will fire the soonest
  • Known issue: There is a known issue with handling daylight savings for scheduled notifications. This functionality may be deprecated to be replaced by another that only deals with elapsed time since epoch instead of a date.

Screenshots #

AndroidiOS

Acknowledgements #

  • Javier Lecuona for submitting the PR that added the ability to have notifications shown daily
  • Jeff Scaturro for submitting the PR to fix the iOS issue around showing daily and weekly notifications and migrating the plugin to AndroidX
  • Ian Cavanaugh for helping create a sample to reproduce the problem reported in issue #88
  • Zhang Jing for adding 'ticker' support for Android notifications
  • ...and everyone else for their contributions. They are greatly appreciated

Getting Started #

The GitHub repository has an example app that should demonstrate of all the supported features of the plugin. Please check the example for more detailed code samples. If you only copy and paste the Dart code then this will not work as there's setup required for each platform. Pub also generates API docs for the latest version here. Besides referring to the example app and getting started section, please ensure that you have performed the steps in the integration guide for each platform further below.

The following samples will demonstrate the more commonly used functionalities.

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

FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
var initializationSettingsAndroid = AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = IOSInitializationSettings(
    onDidReceiveLocalNotification: onDidReceiveLocalNotification);
var initializationSettings = InitializationSettings(
    initializationSettingsAndroid, initializationSettingsIOS);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
    onSelectNotification: selectNotification);

Initialisation should only be done once and the place where this can be done is in the main function of the your application. Alternatively, this 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 Integration section) and designated the function (selectNotification) that should fire when a notification has been tapped on via the onSelectNotification callback. Specifying this callback is entirely optional but here it will trigger navigation to another page and display the payload associated with the notification.

Future selectNotification(String payload) async {
    if (payload != null) {
      debugPrint('notification payload: ' + payload);
    }
    await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => SecondScreen(payload)),
    );
}

In the real world, this payload could represent the id of the item you want to display the details of. Once the initialisation has been done, then you can manage the displaying of notifications.

On iOS, 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.

Notes around initialisation: if the app had been launched by tapping on a notification created by this plugin, calling initialize is what will trigger the onSelectNotification to trigger to handle the notification that the user tapped on. An alternative to handling the "launch notification" is to call the getNotificationAppLaunchDetails method that is available in the plugin. This could be used, for example, to change the home route of the app for deep-linking. Calling initialize will still cause the onSelectNotification callback to fire for the launch notification. It will be up to developers to ensure that they don't process the same notification twice (e.g. by storing and comparing the notification id).

[iOS only] Requesting notification permissions #

The constructor for the IOSInitializationSettings 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.

FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =  FlutterLocalNotificationsPlugin();
var initializationSettingsAndroid =
    AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = IOSInitializationSettings(
        requestSoundPermission: false,
        requestBadgePermission: false,
        requestAlertPermission: false,
        onDidReceiveLocalNotification: onDidReceiveLocalNotification,
    );
var initializationSettings = InitializationSettings(
    initializationSettingsAndroid, initializationSettingsIOS);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
    onSelectNotification: onSelectNotification);

Then call the requestPermissions method with desired permissions at the appropriate point in your application

var result = await flutterLocalNotificationsPlugin
          .resolvePlatformSpecificImplementation<
              IOSFlutterLocalNotificationsPlugin>()
          ?.requestPermissions(
            alert: true,
            badge: true,
            sound: true,
          );

Here the call to flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>() returns the iOS implementation of the plugin that contains APIs specific to iOS if the application is running on iOS. The ?. operator is used here 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 #

var androidPlatformChannelSpecifics = AndroidNotificationDetails(
    'your channel id', 'your channel name', 'your channel description',
    importance: Importance.Max, priority: Priority.High, ticker: 'ticker');
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
    0, 'plain title', 'plain body', platformChannelSpecifics,
    payload: 'item x');

In this block of code, the details for each platform have been specified. This includes the channel details that is required for Android 8.0+. 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. Note that the "ticker" text is passed here though it 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 #

var scheduledNotificationDateTime =
        DateTime.now().add(Duration(seconds: 5));
var androidPlatformChannelSpecifics =
    AndroidNotificationDetails('your other channel id',
        'your other channel name', 'your other channel description');
var iOSPlatformChannelSpecifics =
    IOSNotificationDetails();
NotificationDetails platformChannelSpecifics = NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.schedule(
    0,
    'scheduled title',
    'scheduled body',
    scheduledNotificationDateTime,
    platformChannelSpecifics);

Note that on Android devices, the default behaviour is that the notification may not be delivered at the specified time when the device in a low-power idle mode. This behaviour can be changed by setting the optional parameter named androidAllowWhileIdle to true when calling the schedule method.

Periodically show a notification with a specified interval #

// Show a notification every minute with the first appearance happening a minute after invoking the method
var androidPlatformChannelSpecifics =
    AndroidNotificationDetails('repeating channel id',
        'repeating channel name', 'repeating description');
var iOSPlatformChannelSpecifics =
    IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.periodicallyShow(0, 'repeating title',
    'repeating body', RepeatInterval.EveryMinute, platformChannelSpecifics);

Show a daily notification at a specific time #

var time = Time(10, 0, 0);
var androidPlatformChannelSpecifics =
    AndroidNotificationDetails('repeatDailyAtTime channel id',
        'repeatDailyAtTime channel name', 'repeatDailyAtTime description');
var iOSPlatformChannelSpecifics =
    IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.showDailyAtTime(
    0,
    'show daily title',
    'Daily notification shown at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
    time,
    platformChannelSpecifics);

Show a weekly notification on specific day and time #

var time = Time(10, 0, 0);
var androidPlatformChannelSpecifics =
    AndroidNotificationDetails('show weekly channel id',
        'show weekly channel name', 'show weekly description');
var iOSPlatformChannelSpecifics =
    IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
    androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(
    0,
    'show weekly title',
    'Weekly notification shown on Monday at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
    Day.Monday,
    time,
    platformChannelSpecifics);

Retrieve pending notification requests #

var pendingNotificationRequests =
        await flutterLocalNotificationsPlugin.pendingNotificationRequests();

[Android only] Grouping notifications #

This is a "translation" of the sample available at https://developer.android.com/training/notify-user/group.html For iOS, you could just display the summary notification (not shown in the example) as otherwise the following code would show three notifications

String groupKey = 'com.android.example.WORK_EMAIL';
String groupChannelId = 'grouped channel id';
String groupChannelName = 'grouped channel name';
String groupChannelDescription = 'grouped channel description';
// example based on https://developer.android.com/training/notify-user/group.html
AndroidNotificationDetails firstNotificationAndroidSpecifics =
    AndroidNotificationDetails(
        groupChannelId, groupChannelName, groupChannelDescription,
        importance: Importance.Max,
        priority: Priority.High,
        groupKey: groupKey);
NotificationDetails firstNotificationPlatformSpecifics =
    NotificationDetails(firstNotificationAndroidSpecifics, null);
await flutterLocalNotificationsPlugin.show(1, 'Alex Faarborg',
    'You will not believe...', firstNotificationPlatformSpecifics);
AndroidNotificationDetails secondNotificationAndroidSpecifics =
    AndroidNotificationDetails(
        groupChannelId, groupChannelName, groupChannelDescription,
        importance: Importance.Max,
        priority: Priority.High,
        groupKey: groupKey);
NotificationDetails secondNotificationPlatformSpecifics =
    NotificationDetails(secondNotificationAndroidSpecifics, null);
await flutterLocalNotificationsPlugin.show(
    2,
    'Jeff Chang',
    'Please join us to celebrate the...',
    secondNotificationPlatformSpecifics);

// create the summary notification required for older devices that pre-date Android 7.0 (API level 24)
List<String> lines = List<String>();
lines.add('Alex Faarborg  Check this out');
lines.add('Jeff Chang    Launch Party');
InboxStyleInformation inboxStyleInformation = InboxStyleInformation(
    lines,
    contentTitle: '2 new messages',
    summaryText: 'janedoe@example.com');
AndroidNotificationDetails androidPlatformChannelSpecifics =
    AndroidNotificationDetails(
        groupChannelId, groupChannelName, groupChannelDescription,
        styleInformation: inboxStyleInformation,
        groupKey: groupKey,
        setAsGroupSummary: true);
NotificationDetails platformChannelSpecifics =
    NotificationDetails(androidPlatformChannelSpecifics, null);
await flutterLocalNotificationsPlugin.show(
    3, 'Attention', 'Two new messages', platformChannelSpecifics);

Cancelling/deleting a notification #

// cancel the notification with id value of zero
await flutterLocalNotificationsPlugin.cancel(0);

Cancelling/deleting all notifications #

await flutterLocalNotificationsPlugin.cancelAll();

Get details on if the app was launched via a notification created by this plugin #

 var notificationAppLaunchDetails =
     await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();

This should cover the basic functionality. Please check out the example directory for a sample app that illustrates the rest of the functionality available and refer to the API docs for more information. Also read the below on what you need to configure on each platform

Android integration #

Adding 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.

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.

Note that with 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.

Configuration for scheduled notifications #

If your application needs the ability to schedule notifications then you need to request permissions to be notified when the phone has been booted as scheduled notifications uses the AlarmManager API to determine when notifications should be displayed. However, they are cleared when a phone has been turned off. Requesting permission requires adding the following to the manifest (i.e. your application's AndroidManifest.xml file)

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

The following is also needed to ensure scheduled notifications remain scheduled upon a reboot (this is handled by the plugin)

<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"></action>
    </intent-filter>
</receiver>

Developers will also need to add the following so that plugin can handle displaying scheduled notifications

<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />

If the vibration pattern of an Android notification will be customised then add the following

<uses-permission android:name="android.permission.VIBRATE" />

For reference, the example app's AndroidManifest.xml file can be found here

Release build configuration #

When doing a release build of your app, which is the default setting when building an APK or app bundle, you'll likely need to customise your ProGuard configuration file as per this link and add the following line.

-keep class com.dexterous.** { *; }

After doing so, rules specific to the GSON dependency being used by the plugin will also needed to be added. These rules can be found here. The example app has a consolidated Proguard rules (proguard-rules.pro) file that combines these together for reference here.

You will also need to 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. Without doing this, you might not see the icon you've specified in your app's notifications. The configuration used by the example app can be found here 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).

iOS integration #

General setup #

Add the following lines to the didFinishLaunchingWithOptions method in the AppDelegate.m/AppDelegate.swift file of your iOS project

Objective-C:

if (@available(iOS 10.0, *)) {
  [UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
}

Swift:

if #available(iOS 10.0, *) {
  UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}

By design, iOS applications do not display notifications when they're in the foreground. For iOS 10+, use the presentation options to control the behaviour for when a notification is triggered while the app is in the foreground. For older versions of iOS, you need to handle the callback as part of specifying the method that should be fired to the onDidReceiveLocalNotification argument when creating an instance IOSInitializationSettings object that is passed to the function for initializing the plugin. A snippet below from the sample app shows how this can be done

// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
var initializationSettingsAndroid =
    AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = IOSInitializationSettings(
    onDidReceiveLocalNotification: onDidReceiveLocalNotification);
var initializationSettings = InitializationSettings(
    initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
    onSelectNotification: onSelectNotification);

...

  Future onDidReceiveLocalNotification(
      int id, String title, String body, String payload) async {
    // display a dialog with the notification details, tap ok to go to another page
    showDialog(
      context: context,
      builder: (BuildContext context) => CupertinoAlertDialog(
            title: Text(title),
            content: Text(body),
            actions: [
              CupertinoDialogAction(
                isDefaultAction: true,
                child: Text('Ok'),
                onPressed: () async {
                  Navigator.of(context, rootNavigator: true).pop();
                  await Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => SecondScreen(payload),
                    ),
                  );
                },
              )
            ],
          ),
    );
  }

If you have set notifications to be periodically shown, then on older iOS versions (< 10), if 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:

if(![[NSUserDefaults standardUserDefaults]objectForKey:@"Notification"]){
    [[UIApplication sharedApplication] cancelAllLocalNotifications];
    [[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"Notification"];
}

Swift:

if(!UserDefaults.standard.bool(forKey: "Notification")) {
    UIApplication.shared.cancelAllLocalNotifications()
    UserDefaults.standard.set(true, forKey: "Notification")
}

Custom notification sound restrictions #

When using custom notification sound, developers should be aware that iOS enforces restrictions on this (e.g. supported file formats). As of this writing, this is documented by Apple at

https://developer.apple.com/documentation/usernotifications/unnotificationsound?language=objc

Using firebase_messaging with flutter_local_notifications #

Previously, there were issue that prevented this plugin working properly with the firebase_messaging plugin. This meant that callbacks from each plugin might not be invoked. Version 6.0.13 of firebase_messaging should resolve this issue so please bump your firebase_messaging dependency and follow the steps covered in firebase_messaging's readme file.

Testing #

As the plugin class is not static, it is possible to mock and verify it's behaviour when writing tests as part of your application. Check the source code for a sample test suite can be found at test/flutter_local_notifications_test.dart that demonstrates how this can be done. If you decide to use the plugin class directly as part of your tests, note that the methods will be mostly a no-op and methods that return data will return default values. 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 it's neither Android or iOS, then it defaults to the aforementioned behaviour to reduce friction when writing tests. If this not desired then consider using mocks. Note there is also a named constructor that can be used to pass the platform for the plugin to resolve the desired platform-specific implementation.

[1.4.3] #

  • [Android] added the ability to specify additional flags for the notification. For example, this could be used to allow the audio to repeat. See the API docs and update example app for more details. Thanks to the PR from andylei
  • Minor cleanup of API docs

[1.4.2] #

  • [Android] added the ability to specify the timestamp shown in the notification (issue 596). Thanks to the PR from Nicolas Schneider.
  • Fixed API docs for showWeeklyAtDayAndTime

[1.4.1] #

  • [Android] added the ability to create notification channels before a notification is shown. This can be done by calling the createNotificationChannel within the AndroidFlutterLocalNotificationsPlugin class. This allows applications to create notification channels before a notification is shown. Thanks to the PR from Vladimir Gerashchenko.
  • [Android] added the ability to delete notification channels. This can be done by calling deleteNotificationChannel within AndroidFlutterLocalNotificationsPlugin class.

[1.4.0] #

Please note that there are a number of breaking changes in this release to improve the developer experience when using the plugin APIs. The changes should hopefully be straightforward but please through the changelog carefully just in case. The steps migrate your code has been covered below but the Git history of the example application's main.dart file can also be used as reference.

  • [Android] BREAKING CHANGE The style property of the AndroidNotificationDetails class has been removed as it was redundant. No changes are needed unless your application was displaying media notifications (i.e. style was set to AndroidNotificationStyle.Media). If this is the case, you can migrate your code by setting the styleInformation property of the AndroidNotificationDetails to an instance of the MediaNotificationStyleInformation class. This class is a new addition in this release

  • [Android] BREAKING CHANGE The AndroidNotificationSound abstract class has been introduced to represent Android notification sounds. The sound property of the AndroidNotificationDetails class has changed from being a String type to an AndroidNotificationSound type. In this release, the AndroidNotificationSound has the following subclasses

    • RawResourceAndroidNotificationSound: use this when the sound is raw resource associated with the Android application. Previously, this was the only type of sound supported so applications using the plugin prior to 1.4.0 can migrate their application by using this class. For example, if your previous code was

      var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your other channel id',
        'your other channel name',
        'your other channel description',
        sound: 'slow_spring_board');
      

      Replace it with

      var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your other channel id',
        'your other channel name',
        'your other channel description',
        sound: RawResourceAndroidNotificationSound('slow_spring_board');
      
    • UriAndroidNotificationSound: use this when a URI refers to the sound on the Android device. This is a new feature being supported as part of this release. Developers may need to write their code to access native Android APIs (e.g. the RingtoneManager APIs) to obtain the URIs they need.

  • [Android] BREAKING CHANGE The BitmapSource enum has been replaced by the newly AndroidBitmap abstract class and its subclasses. This removes the need to specify the name/path of the bitmap and the source of the bitmap as two separate properties (e.g. the largeIcon and largeIconBitmapSource properties of the AndroidNotificationDetails class). This change affects the following classes

    • AndroidNotificationDetails: the largeIcon is now an AndroidBitmap type instead of a String and the largeIconBitmapSource property has been removed
    • BigPictureStyleInformation: the largeIcon is now an AndroidBitmap type instead of a String and the largeIconBitmapSource property has been removed. The bigPicture is now a AndroidBitmap type instead of a String and the bigPictureBitmapSource property has been removed

    The following describes how each BitmapSource value maps to the AndroidBitmap subclasses

    • BitmapSource.Drawable -> DrawableResourceAndroidBitmap
    • BitmapSource.FilePath -> FilePathAndroidBitmap

    Each of these subclasses has a constructor that an argument referring to the bitmap itself. For example, if you previously had the following code

      var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your other channel id',
        'your other channel name',
        'your other channel description',
        largeIcon: 'sample_large_icon',
        largeIconBitmapSource: BitmapSource.Drawable,
      )
    

    This would now be replaced with

      var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your other channel id',
        'your other channel name',
        'your other channel description',
        largeIcon: DrawableResourceAndroidBitmap('sample_large_icon'),
      )
    
  • [Android] BREAKING CHANGE The IconSource enum has been replaced by the newly added AndroidIcon abstract class and its subclasses. This change was done for similar reasons in replacing the BitmapSource enum. This only affects the Person class, which is used when displaying each person in a messaging-style notification. Here the icon property is now an AndroidIcon type instead of a String and the iconSource property has been removed.

    The following describes how each IconSource value maps to the AndroidIcon subclasses

    • IconSource.Drawable -> DrawableResourceAndroidIcon
    • IconSource.FilePath -> BitmapFilePathAndroidIcon
    • IconSource.ContentUri -> ContentUriAndroidIcon

    Each of these subclasses has a constructor that accepts an argument referring to the icon itself. For example, if you previously had the following code

    Person(
      icon: 'me',
      iconSource: IconSource.Drawable,
    )
    

    This would now be replaced with

    Person(
      icon: DrawableResourceAndroidIcon('me'),
    )
    

    The AndroidIcon also has a BitmapAssetAndroidIcon subclass to enables the usage of bitmap icons that have been registered as a Flutter asset via the pubspec.yaml file.

  • [Android] BREAKING CHANGE All properties in the AndroidNotificationDetails, DefaultStyleInformation and InboxStyleInformation classes have been made final

  • The DefaultStyleInformation class now implements the StyleInformation class instead of extending it

  • Where possible, classes in the plugins have been updated to provide const constructors

  • Updates to API docs and readme

  • Bump Android dependencies

  • Fixed a grammar issue 0.9.1 changelog entry

[1.3.0] #

  • [iOS] BREAKING CHANGE Plugin will now throw a PlatformException if there was an error returned upon calling the native addNotificationRequest method. Previously the error was logged on the native side the using NSLog function.
  • [iOS] Added ability to associate notifications with attachments. Only applicable to iOS 10+ where the UserNotification APIs are used. Thanks to the PR from Pavel Sipaylo
  • Updated readme on using firebase_messaging together with flutter_local_notifications to let the community that firebase_messaging 6.0.13 can be used to resolve compatibility issues around callbacks when both plugins are used together

[1.2.2] #

  • [Android] Added ability to specify if the timestamp for when a notification occurred should be displayed. Thanks to the PR from mojtabaghiasi

[1.2.1] #

  • [Android] Fixed issue 512 where calling getNotificationAppLaunchDetails() within the onSelectNotification callback could indicating that the app was launched by tapping on a notification when it wasn't the case
  • Update example app to indicate if a notification launched the app and include the launch notification payload

[1.2.0+4] #

  • Title at the top of the readme is now the same name as the plugin
  • Updated readme to add subsections under the Android and iOS integration guide
  • Added links in the readme under the Android release build configuration section that point to the configuration files used by the example app

[1.2.0+3] #

  • Updated API docs for resolvePlatformSpecificImplementation() method

[1.2.0+2] #

  • Make the static values propeerty of the Priority class return List<Priority> instead of being dynamic and added API docs for the property.

[1.2.0+1] #

  • The static values properties for the Day and Importance classes now return List<Day> and List<Importance> respectively instead of being dynamic
  • Added more public API documentation
  • Updated readme to move issues and contributions section to the readme within the repository
  • Added screenshots to readme
  • Updated how breaking changes are highlighted in changelog to all the letters aren't capitalised

[1.2.0] #

  • Added the resolvePlatformSpecificImplementation() method to the FlutterLocalNotificationsPlugin class. This can be used to resolve the underlying platform implementation in order to access platform-specific APIs.
  • Breaking change the static instance properties in the IOSFlutterLocalNotificationsPlugin and AndroidFlutterLocalNotificationsPlugin classes have been removed due to addition of the resolvePlatformSpecificImplementation()
  • Updated readme to remove use of new keyword in code snippets
  • Bumped e2e dependency
  • Bumped example app dependencies
  • Make the pedantic dev_dependency explicit

[1.1.7+1] #

  • Minor update to readme on description around requesting notification permissions
  • Add link to forked firebase_messaging plugin to readme for those that want to use it whilst the PR to fix the compatibility issues with this plugin is waiting to be reviewed

[1.1.7] #

  • [iOS] Added requestPermissions() method to IOSFlutterLocalNotificationsPlugin class. This can be used to request notification permissions separately from plugin initialisation. To facilitate this the IOSFlutterLocalNotificationsPlugin and AndroidFlutterLocalNotificationsPlugin now expose a static instance property that can be used obtain the platform-specific implementation of the plugin so that platform-specific methods can be used. Thanks to the PR from Dariusz Ɓuksza
  • Updated documentation to clarify that getNotificationAppLaunchDetails() is intended to be used more on if a notification from this plugin triggered launch an application
  • Updated API docs for consistency and to better follow the guidelines on effective Dart documentation

[1.1.6] #

  • [iOS] Added ability to set badge number. Thanks to PR from FelixYew
  • Fixed a spelling mistake in the 1.1.5+1 changelog entry

[1.1.5+1] #

  • No functional changes. Fixed a reported formatting issue
  • Mention removal of named constructor argument in 1.1.0 changelog entry
  • Add API docs to FlutterLocalNotificationsPlugin.private() on how it could be used for testing
  • Update notes on testing to mention that the FlutterLocalNotificationsPlugin.private() named constructor may be of use

[1.1.5] #

  • [Android] minor optimisation on scheduling related code so that Gson instance is reused instead of being rebuilt each time
  • Changed plugin to require 1.12.3+hotfix.5 or greater since pub has issues resolving 1.12.3+hotfix.6
  • Updated changelog entry for version 1.1.4 to mention removal of upper bound constraint on Flutter SDK requirement

[1.1.4] #

  • Support v2 Android embedding. Note that there is currently a known issue in the Flutter SDK that will cause onSelectNotification to fire twice on Android. The fix is in the master channel but hasn't rolled out to other channels. Subscribe to the issue for updates.
  • Require Flutter SDK 1.12.3+hotfix.6 or greater. Maximum SDK restraint has also been removed

[1.1.3] #

  • Expose NotificationAppLaunchDetails via main plugin
  • Retroactively updated changelog for 1.1.0 to indicate breaking change on moving to using platform interface
  • Made plugin methods be a no-op to fix issue with version 1.1.0 where test code involving the plugin would fail when running on an environment that is neither Android or iOS

[1.1.2] #

  • Passing a null notification id now throws an ArgumentError. Thanks to PR from talmor_guy
  • Slight tweak to message displayed with by ArgumentError when notification id is not within range of a 32-bit integer

[1.1.1] #

  • [Android] Added ability to specify timeout duration of notification
  • [Android] Added ability to specify the notification category

[1.1.0] #

  • Breaking change Updated plugin to make use of flutter_local_notifications_platform_interface version 1.0.1. This allows for platform-specific implementations of the platform interface to now be accessible. Note that the plugin will check which platform the plugin is running on. Note: this may have inadvertently broke some tests for users as the plugin now checks which platform the plugin is executing code on and would throw an UnimplementedError since neither iOS or Android can be detected. Another issue is that NotificationAppLaunchDetails was no longer exposed via the main plugin. Please upgrade to 1.1.3 to have both of these issues fixed
  • Breaking change Plugin callbacks are no longer publicly accessible
  • Breaking change [iOS] Local notifications that launched the app should now only be processed by the plugin if they were created by the plugin.
  • Breaking change MethodChannel argument has been removed from the named constructor that was visible for testing purposes

[1.0.0] #

  • Breaking change [iOS] Added checks to ensure callbacks are only invoked for notifications originating from the plugin to improve compatibility with other notification plugins.
  • [Android] Bump Gradle plugin to 3.5.3

[0.9.1+3] #

  • Include notes in getting started section to emphasise that the steps in the integration guide for each platform needs to be done.
  • Move information in the readme on configuring resources to keep on Android.

[0.9.1+2] #

  • Update link to repository due to restructuring.

[0.9.1+1] #

  • Update readme with Swift example code on cancelling local notifications using the deprecated UILocalNotification iOS APIs when trying to prevent local notifications from appearing in the scenario where user has uninstalled the app whilst there are pending notification requests, reinstalled the app and ran it again.

[0.9.1] #

  • Added support for media notifications. This currently only supports showing the specified image as album artwork. Thanks to the PR by gianlucaparadise

[0.9.0+1] #

  • Fix readme where Objective-C was written twice

[0.9.0] #

  • [Android] Add ability to customise visibility of a notification on the lockscreen. Thanks to PR by gianlucaparadise
  • [Android] Bumped compile and target SDK to 29
  • Breaking change [iOS] Plugin no longer registers as a UNUserNotificationCenterDelegate. This is to enable compatibility with other plugins that display notifications. Developers must now do this themselves. Refer to the updated iOS integration section for more info on this
  • Updated info about configuring Proguard configuration rules and included a file that could be used for reference in the example app
  • Removed dependency on the meta package
  • Breaking change Now requires Flutter SDK 1.10.0 or greater
  • Migrate the plugin to the pubspec platforms manifest

[0.8.4+3] #

  • Update example to fix issue 372 around app not firing onSelectNotification having switched to using streams and initialising the app in the main function.

[0.8.4+2] #

  • Add note to readme that plugin initialisation be done as part of showing the first page of the app.

[0.8.4+1] #

  • Fix typo in readme. Thanks to PR submitted by Michael Arndt
  • Updated API docs and example around initializing the plugin to make it clearer that initialize should only be called once on app startup.

[0.8.4] #

  • [iOS] Fix issue 336 where a native crash occurred after creating a notification with a null body

[0.8.3] #

  • [Android] Changed intents to use the FLAG_UPDATE_CURRENT flag instead of FLAG_CANCEL_CURRENT as alarms weren't being cleared out properly when updating or cancel a notification. Thanks to WJQ for submitting the PR to address the cancellation issue

[0.8.2+1] #

  • Remove ScheduledAndroidNotificationPrecision enum that wasn't being used
  • Update readme around approach used to develop the plugin

[0.8.2] #

  • [iOS] Fix issue 295 where onSelectNotification callback wasn't trigger when a notification had been tapped on whilst the app was terminated

[0.8.1+1] #

  • Update comment in example around grouped notifications to clarify that the summary notification ia required for all versions of Android
  • Update email address in pubspec.yaml

[0.8.1] #

  • [iOS] Accepted PR from Josh Burton that improves ability for plugin to work in multiple isolate by moving state to instance variables
  • [iOS] Add a guard to prevent a scenario from happening where it may still be possible for the onDidReceiveLocalNotification callback to trigger on iOS 10+
  • Minor update to readme on raising issues and correction on payload information

[0.8.0] #

  • Added an optional parameter named androidAllowWhileIdle to schedule method. This will allow notifications to still display at the specified time when the Android device is in an low-power idle mode.
  • Breaking change Bump minimum Flutter version to 1.5.0
  • Breaking change Update Flutter dependencies

[0.7.1+3] #

  • Fix build status badge

[0.7.1+2] #

  • Started adding Cirrus CI configuration and include CI status badge as part of readme

[0.7.1+1] #

  • Updated readme to include information about OS limits related to scheduled notifications

[0.7.1] #

  • [Android] Added ability to specify the "ticker" text. Thanks to PR submitted by Zhang Jing
  • Spelling mistake fixes in readme. Thanks to PR submitted by Wanbok (Wayne) Choi

[0.7.0] #

  • Breaking change [Android] Updated to Gradle 5.1.1 and Android Gradle plugin has been updated to 3.4.0 (aligns with Android Studio 3.4 release). Example app has also been updated to Gradle 5.1.1. Apps will need to update to use the plugin. Please see here for more information if you need help on updating
  • [Android] Add ability to specify the LED colour of the notification. Updated example app to show this can be done. Note that for Android 8.0+ (i.e. API 26+) that this is tied to the notification channel

[0.6.1] #

  • [Android/iOS] Added pendingNotificationRequests method. This will return a list of pending notification requests that have been scheduled to be shown in the future. Updated example app to include sample code for calling the method
  • [Android] Fix an issue where scheduling a notification (recurring or otherwise) with the same id as another notification that was scheduled with the same id would result in both being stored in shared preferences. The shared preferences were used to reschedule notifications on reboot and shouldn't affect the functionality of displaying the notifications
  • Updated plugin methods to return Future<void> instead of Future as per Dart guidelines
  • Updated readme to mention known issue with scheduling notifications and daylight savings
  • Refactored widgets in example app

[0.6.0] #

  • Breaking change [Android] Updated Gradle plugin to 3.3.2
  • Breaking change [Android] Changed to store the name of drawable specified as the small icon to be used for Android notifications instead of the resource ID. This should fix the scenario where an app could be updated and the resource IDs got change and cause scheduled notifications to not appear. Believe this fix should retroactively apply for notifications scheduled with an icon specified but won't apply to those that were scheduled to use the default icon specified via the initialize method. This is due to the fact the name of the default icon wasn't being cached in previous ones but this has now changed so it's cached in shared preferences from this version onwards

[0.5.2] #

  • Fix for when multiple isolates use the plugin. Thanks to PR submitted by xtelinco
  • Added the channelAction field to the AndroidNotificationDetails class. This provides options for managing notification channels on Android. Default behaviour is to create the notification channel if it doesn't exist (which was what it it use to do). Another option is to update the details of the notification channel. Example app has been updated to demonstrate how to update a notification channel when sending a notification

[0.5.1+2] #

  • Highlight note on migrating to AndroidX more in readme

[0.5.1+1] #

  • Readme corrections and add information on migrating to AndroidX

[0.5.1] #

  • Updated Gradle plugin of example app to 3.3.1
  • Added support for messaging style notifications on Android as requested in issue 159. See example app for some sample code

[0.5.0] #

  • Breaking change Migrated to use AndroidX as the Android support libraries are deprecated. There shouldn't be any functional changes. Developers may require migrating their apps to support this following this guide. This addresses issue 162. Thanks to Jeff Scaturro for submitting the PR for this work. Note that if you don't want to migrate your app to use AndroidX yet then you may need to pin dependencies to a specific version

[0.4.5] #

  • Fix issue 160 so that notifications created with the schedule on Android will appear at the exact date and time they are scheduled

[0.4.4+2] #

  • Fix changelog

[0.4.4+1] #

  • Breaking change Fix naming of onDidReceiveLocalNotification property in the IOSInitializationSettings class (was previously named onDidReceiveLocalNotificationCallback by accident)

[0.4.4] #

  • Breaking change removed registerUNNotificationCenterDelegate argument for the IOSInitializationSettings class as it wasn't actually used.
  • Plugin can now handle didReceiveLocalNotification delegate method in iOS and allow developers to handle the associated callback in Flutter. Added a onDidReceiveLocalNotificationCallback argument to the IOSInitializationSettings class to enable this and updated the sample code to demonstrate the usage. This should resolve issue 14.

[0.4.3] #

  • Merged PR from Aine LLC (ganessaa) to fix issue 140 where scheduled notifications were shown immediately on iOS versions before 10. Note that this issue is likely related to an known issue in the Flutter engine that may require switching channels to be addressed as the fix isn't on the stable channel yet.
  • [Android] Provide a way to hide the large icon when showing an expanded big picture notification via the hideExpandedLargeIcon flag within thr BigPictureStyleInformation class. This provides a solution for issue 136. Updated the example to demonstrate
  • Merged PR from (riccardoratta) so that sample code is coloured in GitHub to improve readability.

[0.4.2+1] #

  • Update changelog to indicate when MessageHandler typedef got renamed (in 0.4.1) as raised in issue 132

[0.4.2] #

  • Breaking change Fix issue 127 by changing plugin to Android Support Library version 27.1.1, compile and target SDK version to 27 due to issues Flutter has with API 28.

[0.4.1+1] #

  • Remove unused code in example app

[0.4.1] #

  • Breaking change renamed the selectNotification callback exposed by the initialize function to onSelectNotification
  • Breaking change renamed the MessageHandler typedef to SelectNotificationCallback
  • Breaking change updated plugin to Android Support Library version 28.0, compile and target SDK version to 28
  • Address issue 115 by adding validation to the notification ID values. This ensure they're within the range of a 32-bit integer as notification IDs on Android need to be within that range. Note that an ArgumentError is thrown when a value is out of range.
  • Updated the Android Integration section around registering receivers via the Android manifest as per the suggestion in 116
  • Updated version of the http dependency for used by the example app

[0.4.0] #

  • [Android] Fix issue 112 where big picture notifications wouldn't show

[0.3.9] #

  • [Android] Added ability to show progress notifications and updated example app to demonstrate how to display them

[0.3.8] #

  • Added getNotificationAppLaunchDetails() method that could be used to determine if the app was launched via notification (raised in issue 99)
  • Added documentation around ProGuard configuration to Android Integration section of the readme

[0.3.7] #

  • [Android] Fix issue 88 where cancelled notifications could reappear on reboot.

[0.3.6] #

  • [Android] Add mapping to the setOnlyAlertOnce method 83. Allows the sound, vibrate and ticker to be played if the notification is not already showing
  • [Android] Add mapping to setShowBadge for notification channels that controls if notifications posted to channel can appear as application icon badges in a Launcher

[0.3.5] #

  • [Android] Will now throw a PlatformException with a more friendly error message on the Flutter side when a specified resource hasn't been found e.g. when specifying the icon for the notification
  • Fix overflow rendering issue in the example app

[0.3.4] #

  • [Android] Fix issue 71 where the wrong time on when the notification occurred is being displayed. Breaking change this involves changing it the receiver for displaying a scheduled notification will only build the notification prior to displaying it. There is a fix applied to existing scheduled notifications in this release that will be eventually be removed as going forward all scheduled notifications will work as just described
  • [Android] Fix an issue with serialising and deserialising the notifications so that additional style types (big picture and inbox) would be recognised. This affected scheduled notifications where upon rebooting the device, the plugin would need to reschedule the notifications using information saved in shared preferences.

[0.3.3] #

  • [iOS] Fixes issue 61 where the showDailyAtTime and showWeeklyAtDayAndTime methods may not show notifications that should appear the next day. Thanks to Jeff Scaturro for submitting the PR to fix this.

[0.3.2] #

  • No functional changes. Updated the readme around raising issues, recurring Android notifications on Android and a fix in the getting started section (thanks to ebeem for spotting that).

[0.3.1] #

  • No functional changes in this release but removed a class that is no longer used due to changes in 0.3.0
  • Updated readme information the example app and configuring Android notification icons
  • changelog is now in reverse chronological order so details about the most recent release are at the top
  • Additional comments in the example's main.dart file to refer to downloading the complete example app project from GitHub

[0.3.0] #

  • BREAKING CHANGES restructured code so that only a single import statement is now needed to use the plugin. Classes that had the platform (Android/iOS) as a suffix are now prefixes to improve readability of code and follow the recommendations for writing Dart code i.e. write code that reads more like a sentence. The following have been renamed

    • InitializationSettingsAndroid -> AndroidInitializationSettings
    • InitializationSettingsIOS -> IOSInitializationSettings
    • NotificationDetailsAndroid -> AndroidNotificationDetails
    • NotificationStyleAndroid -> AndroidNotificationStyle
    • NotificationDetailsIOS -> IOSNotificationDetails
  • [Android] Ability to set the large icon used for each notification. See example app for sample code

  • [Android] Ability to create a notification with the big picture style. See example app for sample code

  • Correct license text

[0.2.9] #

  • Fix error in calling initialize error on iOS versions < 9

[0.2.8] #

[0.2.7] #

  • [Android] Fix issue with showDailyAtTime and showWeeklyAtDayAndTime where time occurred in the past and caused notification to trigger instantly

[0.2.6] #

  • [Android] Fix bug with applying ongoing and autoCancel properties

[0.2.5] #

  • [Android] Bug fix for previous release

[0.2.4] #

  • [Android] Add ability to set the colour.

[0.2.3] #

  • [Android/iOS] Add ability to have a notification shown daily at a specified time. Credits to Javier Lecuona for submitting the PR for this.
  • [Android/iOS] Add ability to have a notification shown weekly on a specific day and time.

[0.2.2] #

  • [Android/iOS] Fix RepeatInterval not being mapped to the correct values on the native side. Thanks to Thibault Deckers for spotting the issue.

[0.2.1] #

  • [Android/iOS] Add ability to set a notification to be periodically displayed
  • [Android] Fix a bug where the small icon could not be be found when loading scheduled notifications from shared preferences to reschedule them on a device reboot
  • [Android] Fix example app manifest file

[0.2.0] #

  • [Android] Add ability to specify if notifications should automatically be dismissed upon touching them
  • [Android] Add ability to specify in notifications are ongoing
  • [Android] Fix bug in cancelling all notifications

[0.1.9] #

  • [Android/iOS] Add ability to cancel/remove all notifications

[0.1.8] #

  • [Android] Bug fix for grouping notifications

[0.1.7] #

  • [Android] Add ability to show grouped notifications. Example code has been updated to demonstrate this functionality.
  • Fixed the example project so it works with the new release of Cocoapods (1.5.0)
  • Fixes for when API methods were called without specifying platform specific settings

[0.1.6] #

  • BREAKING CHANGES Apologies again, this is another cleanup release. FlutterLocalNotifications class has been renamed to FlutterLocalNotificationsPlugin now as it makes more sense from a readability perspective. More importantly, the class and methods are also no longer static to faciliate mocking and testing. It's something I should've picked up on earlier so sorry once again. Check the source code for an example on how to mock the plugin when testing

[0.1.5] #

  • BREAKING CHANGES There are no functional changes. This is an API cleanup release where I've reorganised the Dart classes to better separate them by platform. What this means is that the import statements in your coode will need to be fixed. Apologies to anyone using the plugin but I feel that this was necessary as Flutter may target additional platforms in the future. Hopefully you'll agree that the end result looks a better :)

[0.1.4] #

  • [Android] Add inbox notification style

[0.1.3] #

  • Fix broken example app for iOS due to incorrect reference to custom sound file. Added ability to handle when a notification is tapped. See updated example for details on how to do this and will navigate to another page. Note that the second page isn't rendering full-screen on Android if the notification was tapped on while the app was in the foreground. Suspect that this is Flutter rendering issue and have logged this on the Flutter repository at https://github.com/flutter/flutter/issues/16636

[0.1.2] #

  • [Android] Bug fix in calculating when to show a scheduled notification. Ensure scheduled Android notifications will remain scheduled even after rebooting.

[0.1.1] #

  • [Android] Add ability to use HTML markup to format the title and content of notifications

[0.1.0] #

  • [Android] Add support for big text style for and being able format the big text style specific content using HTML markup.

[0.0.9] #

  • [iOS] Enable ability to customise the sound for notifications (Important requires testing on older iOS versions < 10)
  • [iOS] Can now specify default presentation options (Breaking change named parameters for iOS initialisation have changed) that can also be overridden at the notification level).
  • [iOS] Fixes for reading in specified options

[0.0.8] #

  • [Android] Enable ability to customise sound and vibration for notifications.

[0.0.7] #

  • [Android] Fix notifications so that tapping on them will remove them and will also start the app if it has been terminated.

[0.0.6] #

  • [iOS] Add ability to customise the presentation options when a notification is triggered while the app is in the foreground for notifications presented using the User Notifications Framework (iOS 10+). IMPORTANT: the named parameters for iOS initialisation settings constructor have had to change to differentiate between permission options and presentation options

[0.0.5] #

  • [iOS] Updates to code to support both legacy notifications via UILocalNotification (before iOS 10) and the User Notifications framework introduced in iOS 10

[0.0.4] #

  • Updated readme

[0.0.3] #

  • Fix changelog

[0.0.2] #

  • Readme fixes

[0.0.1] #

  • Initial release

example/lib/main.dart

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:rxdart/subjects.dart';

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

// Streams are created so that app can respond to notification-related events since the plugin is initialised in the `main` function
final BehaviorSubject<ReceivedNotification> didReceiveLocalNotificationSubject =
    BehaviorSubject<ReceivedNotification>();

final BehaviorSubject<String> selectNotificationSubject =
    BehaviorSubject<String>();

NotificationAppLaunchDetails notificationAppLaunchDetails;

class ReceivedNotification {
  final int id;
  final String title;
  final String body;
  final String payload;

  ReceivedNotification({
    @required this.id,
    @required this.title,
    @required this.body,
    @required this.payload,
  });
}

/// IMPORTANT: running the following code on its own won't work as there is setup required for each platform head project.
/// Please download the complete example app from the GitHub repository where all the setup has been done
Future<void> main() async {
  // needed if you intend to initialize in the `main` function
  WidgetsFlutterBinding.ensureInitialized();

  notificationAppLaunchDetails =
      await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();

  var initializationSettingsAndroid = AndroidInitializationSettings('app_icon');
  // Note: permissions aren't requested here just to demonstrate that can be done later using the `requestPermissions()` method
  // of the `IOSFlutterLocalNotificationsPlugin` class
  var initializationSettingsIOS = IOSInitializationSettings(
      requestAlertPermission: false,
      requestBadgePermission: false,
      requestSoundPermission: false,
      onDidReceiveLocalNotification:
          (int id, String title, String body, String payload) async {
        didReceiveLocalNotificationSubject.add(ReceivedNotification(
            id: id, title: title, body: body, payload: payload));
      });
  var initializationSettings = InitializationSettings(
      initializationSettingsAndroid, initializationSettingsIOS);
  await flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onSelectNotification: (String payload) async {
    if (payload != null) {
      debugPrint('notification payload: ' + payload);
    }
    selectNotificationSubject.add(payload);
  });
  runApp(
    MaterialApp(
      home: HomePage(),
    ),
  );
}

class PaddedRaisedButton extends StatelessWidget {
  final String buttonText;
  final VoidCallback onPressed;

  const PaddedRaisedButton({
    @required this.buttonText,
    @required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 8.0),
      child: RaisedButton(child: Text(buttonText), onPressed: onPressed),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final MethodChannel platform =
      MethodChannel('crossingthestreams.io/resourceResolver');
  @override
  void initState() {
    super.initState();
    _requestIOSPermissions();
    _configureDidReceiveLocalNotificationSubject();
    _configureSelectNotificationSubject();
  }

  void _requestIOSPermissions() {
    flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            IOSFlutterLocalNotificationsPlugin>()
        ?.requestPermissions(
          alert: true,
          badge: true,
          sound: true,
        );
  }

  void _configureDidReceiveLocalNotificationSubject() {
    didReceiveLocalNotificationSubject.stream
        .listen((ReceivedNotification receivedNotification) async {
      await showDialog(
        context: context,
        builder: (BuildContext context) => CupertinoAlertDialog(
          title: receivedNotification.title != null
              ? Text(receivedNotification.title)
              : null,
          content: receivedNotification.body != null
              ? Text(receivedNotification.body)
              : null,
          actions: [
            CupertinoDialogAction(
              isDefaultAction: true,
              child: Text('Ok'),
              onPressed: () async {
                Navigator.of(context, rootNavigator: true).pop();
                await Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) =>
                        SecondScreen(receivedNotification.payload),
                  ),
                );
              },
            )
          ],
        ),
      );
    });
  }

  void _configureSelectNotificationSubject() {
    selectNotificationSubject.stream.listen((String payload) async {
      await Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => SecondScreen(payload)),
      );
    });
  }

  @override
  void dispose() {
    didReceiveLocalNotificationSubject.close();
    selectNotificationSubject.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Plugin example app'),
        ),
        body: SingleChildScrollView(
          scrollDirection: Axis.vertical,
          child: Padding(
            padding: EdgeInsets.all(8.0),
            child: Center(
              child: Column(
                children: <Widget>[
                  Padding(
                    padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 8.0),
                    child: Text(
                        'Tap on a notification when it appears to trigger navigation'),
                  ),
                  Padding(
                    padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 8.0),
                    child: Text.rich(
                      TextSpan(
                        children: [
                          TextSpan(
                            text: 'Did notification launch app? ',
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                          TextSpan(
                            text:
                                '${notificationAppLaunchDetails?.didNotificationLaunchApp ?? false}',
                          )
                        ],
                      ),
                    ),
                  ),
                  if (notificationAppLaunchDetails?.didNotificationLaunchApp ??
                      false)
                    Padding(
                      padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 8.0),
                      child: Text.rich(
                        TextSpan(
                          children: [
                            TextSpan(
                              text: 'Launch notification payload: ',
                              style: TextStyle(fontWeight: FontWeight.bold),
                            ),
                            TextSpan(
                              text: notificationAppLaunchDetails.payload,
                            )
                          ],
                        ),
                      ),
                    ),
                  PaddedRaisedButton(
                    buttonText: 'Show plain notification with payload',
                    onPressed: () async {
                      await _showNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Show plain notification that has no body with payload',
                    onPressed: () async {
                      await _showNotificationWithNoBody();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Show plain notification with payload and update channel description [Android]',
                    onPressed: () async {
                      await _showNotificationWithUpdatedChannelDescription();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Show plain notification as public on every lockscreen [Android]',
                    onPressed: () async {
                      await _showPublicNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Cancel notification',
                    onPressed: () async {
                      await _cancelNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Schedule notification to appear in 5 seconds, custom sound, red colour, large icon, red LED',
                    onPressed: () async {
                      await _scheduleNotification();
                    },
                  ),
                  Text(
                      'NOTE: red colour, large icon and red LED are Android-specific'),
                  PaddedRaisedButton(
                    buttonText: 'Repeat notification every minute',
                    onPressed: () async {
                      await _repeatNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Repeat notification every day at approximately 10:00:00 am',
                    onPressed: () async {
                      await _showDailyAtTime();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Repeat notification weekly on Monday at approximately 10:00:00 am',
                    onPressed: () async {
                      await _showWeeklyAtDayAndTime();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show notification with no sound',
                    onPressed: () async {
                      await _showNotificationWithNoSound();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Show notification using Android Uri sound [Android]',
                    onPressed: () async {
                      await _showSoundUriNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Show notification that times out after 3 seconds [Android]',
                    onPressed: () async {
                      await _showTimeoutNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show insistent notification [Android]',
                    onPressed: () async {
                      await _showInsistentNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show big picture notification [Android]',
                    onPressed: () async {
                      await _showBigPictureNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Show big picture notification, hide large icon on expand [Android]',
                    onPressed: () async {
                      await _showBigPictureNotificationHideExpandedLargeIcon();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show media notification [Android]',
                    onPressed: () async {
                      await _showNotificationMediaStyle();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show big text notification [Android]',
                    onPressed: () async {
                      await _showBigTextNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show inbox notification [Android]',
                    onPressed: () async {
                      await _showInboxNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show messaging notification [Android]',
                    onPressed: () async {
                      await _showMessagingNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show grouped notifications [Android]',
                    onPressed: () async {
                      await _showGroupedNotifications();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show ongoing notification [Android]',
                    onPressed: () async {
                      await _showOngoingNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Show notification with no badge, alert only once [Android]',
                    onPressed: () async {
                      await _showNotificationWithNoBadge();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Show progress notification - updates every second [Android]',
                    onPressed: () async {
                      await _showProgressNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Show indeterminate progress notification [Android]',
                    onPressed: () async {
                      await _showIndeterminateProgressNotification();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Check pending notifications',
                    onPressed: () async {
                      await _checkPendingNotificationRequests();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Cancel all notifications',
                    onPressed: () async {
                      await _cancelAllNotifications();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show notification with icon badge [iOS]',
                    onPressed: () async {
                      await _showNotificationWithIconBadge();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show notification without timestamp [Android]',
                    onPressed: () async {
                      await _showNotificationWithoutTimestamp();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText:
                        'Show notification with custom timestamp [Android]',
                    onPressed: () async {
                      await _showNotificationWithCustomTimestamp();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Show notification with attachment [iOS]',
                    onPressed: () async {
                      await _showNotificationWithAttachment();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Create notification channel [Android]',
                    onPressed: () async {
                      await _createNotificationChannel();
                    },
                  ),
                  PaddedRaisedButton(
                    buttonText: 'Delete notification channel [Android]',
                    onPressed: () async {
                      await _deleteNotificationChannel();
                    },
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }

  Future<void> _showNotification() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your channel id', 'your channel name', 'your channel description',
        importance: Importance.Max, priority: Priority.High, ticker: 'ticker');
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0, 'plain title', 'plain body', platformChannelSpecifics,
        payload: 'item x');
  }

  Future<void> _showNotificationWithNoBody() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your channel id', 'your channel name', 'your channel description',
        importance: Importance.Max, priority: Priority.High, ticker: 'ticker');
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0, 'plain title', null, platformChannelSpecifics,
        payload: 'item x');
  }

  Future<void> _cancelNotification() async {
    await flutterLocalNotificationsPlugin.cancel(0);
  }

  /// Schedules a notification that specifies a different icon, sound and vibration pattern
  Future<void> _scheduleNotification() async {
    var scheduledNotificationDateTime =
        DateTime.now().add(Duration(seconds: 5));
    var vibrationPattern = Int64List(4);
    vibrationPattern[0] = 0;
    vibrationPattern[1] = 1000;
    vibrationPattern[2] = 5000;
    vibrationPattern[3] = 2000;

    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your other channel id',
        'your other channel name',
        'your other channel description',
        icon: 'secondary_icon',
        sound: RawResourceAndroidNotificationSound('slow_spring_board'),
        largeIcon: DrawableResourceAndroidBitmap('sample_large_icon'),
        vibrationPattern: vibrationPattern,
        enableLights: true,
        color: const Color.fromARGB(255, 255, 0, 0),
        ledColor: const Color.fromARGB(255, 255, 0, 0),
        ledOnMs: 1000,
        ledOffMs: 500);
    var iOSPlatformChannelSpecifics =
        IOSNotificationDetails(sound: 'slow_spring_board.aiff');
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.schedule(
        0,
        'scheduled title',
        'scheduled body',
        scheduledNotificationDateTime,
        platformChannelSpecifics);
  }

  Future<void> _showNotificationWithNoSound() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'silent channel id',
        'silent channel name',
        'silent channel description',
        playSound: false,
        styleInformation: DefaultStyleInformation(true, true));
    var iOSPlatformChannelSpecifics =
        IOSNotificationDetails(presentSound: false);
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(0, '<b>silent</b> title',
        '<b>silent</b> body', platformChannelSpecifics);
  }

  Future<void> _showSoundUriNotification() async {
    // this calls a method over a platform channel implemented within the example app to return the Uri for the default
    // alarm sound and uses as the notification sound
    String alarmUri = await platform.invokeMethod('getAlarmUri');
    final x = UriAndroidNotificationSound(alarmUri);
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'uri channel id', 'uri channel name', 'uri channel description',
        sound: x,
        playSound: true,
        styleInformation: DefaultStyleInformation(true, true));
    var iOSPlatformChannelSpecifics =
        IOSNotificationDetails(presentSound: false);
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0, 'uri sound title', 'uri sound body', platformChannelSpecifics);
  }

  Future<void> _showTimeoutNotification() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'silent channel id',
        'silent channel name',
        'silent channel description',
        timeoutAfter: 3000,
        styleInformation: DefaultStyleInformation(true, true));
    var iOSPlatformChannelSpecifics =
        IOSNotificationDetails(presentSound: false);
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(0, 'timeout notification',
        'Times out after 3 seconds', platformChannelSpecifics);
  }

  Future<void> _showInsistentNotification() async {
    // This value is from: https://developer.android.com/reference/android/app/Notification.html#FLAG_INSISTENT
    var insistentFlag = 4;
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your channel id', 'your channel name', 'your channel description',
        importance: Importance.Max,
        priority: Priority.High,
        ticker: 'ticker',
        additionalFlags: Int32List.fromList([insistentFlag]));
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0, 'insistent title', 'insistent body', platformChannelSpecifics,
        payload: 'item x');
  }

  Future<String> _downloadAndSaveFile(String url, String fileName) async {
    var directory = await getApplicationDocumentsDirectory();
    var filePath = '${directory.path}/$fileName';
    var response = await http.get(url);
    var file = File(filePath);
    await file.writeAsBytes(response.bodyBytes);
    return filePath;
  }

  Future<void> _showBigPictureNotification() async {
    var largeIconPath = await _downloadAndSaveFile(
        'http://via.placeholder.com/48x48', 'largeIcon');
    var bigPicturePath = await _downloadAndSaveFile(
        'http://via.placeholder.com/400x800', 'bigPicture');
    var bigPictureStyleInformation = BigPictureStyleInformation(
        FilePathAndroidBitmap(bigPicturePath),
        largeIcon: FilePathAndroidBitmap(largeIconPath),
        contentTitle: 'overridden <b>big</b> content title',
        htmlFormatContentTitle: true,
        summaryText: 'summary <i>text</i>',
        htmlFormatSummaryText: true);
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'big text channel id',
        'big text channel name',
        'big text channel description',
        styleInformation: bigPictureStyleInformation);
    var platformChannelSpecifics =
        NotificationDetails(androidPlatformChannelSpecifics, null);
    await flutterLocalNotificationsPlugin.show(
        0, 'big text title', 'silent body', platformChannelSpecifics);
  }

  Future<void> _showBigPictureNotificationHideExpandedLargeIcon() async {
    var largeIconPath = await _downloadAndSaveFile(
        'http://via.placeholder.com/48x48', 'largeIcon');
    var bigPicturePath = await _downloadAndSaveFile(
        'http://via.placeholder.com/400x800', 'bigPicture');
    var bigPictureStyleInformation = BigPictureStyleInformation(
        FilePathAndroidBitmap(bigPicturePath),
        hideExpandedLargeIcon: true,
        contentTitle: 'overridden <b>big</b> content title',
        htmlFormatContentTitle: true,
        summaryText: 'summary <i>text</i>',
        htmlFormatSummaryText: true);
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'big text channel id',
        'big text channel name',
        'big text channel description',
        largeIcon: FilePathAndroidBitmap(largeIconPath),
        styleInformation: bigPictureStyleInformation);
    var platformChannelSpecifics =
        NotificationDetails(androidPlatformChannelSpecifics, null);
    await flutterLocalNotificationsPlugin.show(
        0, 'big text title', 'silent body', platformChannelSpecifics);
  }

  Future<void> _showNotificationMediaStyle() async {
    var largeIconPath = await _downloadAndSaveFile(
        'http://via.placeholder.com/128x128/00FF00/000000', 'largeIcon');
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
      'media channel id',
      'media channel name',
      'media channel description',
      largeIcon: FilePathAndroidBitmap(largeIconPath),
      styleInformation: MediaStyleInformation(),
    );
    var platformChannelSpecifics =
        NotificationDetails(androidPlatformChannelSpecifics, null);
    await flutterLocalNotificationsPlugin.show(
        0, 'notification title', 'notification body', platformChannelSpecifics);
  }

  Future<void> _showBigTextNotification() async {
    var bigTextStyleInformation = BigTextStyleInformation(
        'Lorem <i>ipsum dolor sit</i> amet, consectetur <b>adipiscing elit</b>, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
        htmlFormatBigText: true,
        contentTitle: 'overridden <b>big</b> content title',
        htmlFormatContentTitle: true,
        summaryText: 'summary <i>text</i>',
        htmlFormatSummaryText: true);
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'big text channel id',
        'big text channel name',
        'big text channel description',
        styleInformation: bigTextStyleInformation);
    var platformChannelSpecifics =
        NotificationDetails(androidPlatformChannelSpecifics, null);
    await flutterLocalNotificationsPlugin.show(
        0, 'big text title', 'silent body', platformChannelSpecifics);
  }

  Future<void> _showInboxNotification() async {
    var lines = List<String>();
    lines.add('line <b>1</b>');
    lines.add('line <i>2</i>');
    var inboxStyleInformation = InboxStyleInformation(lines,
        htmlFormatLines: true,
        contentTitle: 'overridden <b>inbox</b> context title',
        htmlFormatContentTitle: true,
        summaryText: 'summary <i>text</i>',
        htmlFormatSummaryText: true);
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'inbox channel id', 'inboxchannel name', 'inbox channel description',
        styleInformation: inboxStyleInformation);
    var platformChannelSpecifics =
        NotificationDetails(androidPlatformChannelSpecifics, null);
    await flutterLocalNotificationsPlugin.show(
        0, 'inbox title', 'inbox body', platformChannelSpecifics);
  }

  Future<void> _showMessagingNotification() async {
    // use a platform channel to resolve an Android drawable resource to a URI.
    // This is NOT part of the notifications plugin. Calls made over this channel is handled by the app
    String imageUri = await platform.invokeMethod('drawableToUri', 'food');
    var messages = List<Message>();
    // First two person objects will use icons that part of the Android app's drawable resources
    var me = Person(
      name: 'Me',
      key: '1',
      uri: 'tel:1234567890',
      icon: DrawableResourceAndroidIcon('me'),
    );
    var coworker = Person(
      name: 'Coworker',
      key: '2',
      uri: 'tel:9876543210',
      icon: FlutterBitmapAssetAndroidIcon('icons/coworker.png'),
    );
    // download the icon that would be use for the lunch bot person
    var largeIconPath = await _downloadAndSaveFile(
        'http://via.placeholder.com/48x48', 'largeIcon');
    // this person object will use an icon that was downloaded
    var lunchBot = Person(
      name: 'Lunch bot',
      key: 'bot',
      bot: true,
      icon: BitmapFilePathAndroidIcon(largeIconPath),
    );
    messages.add(Message('Hi', DateTime.now(), null));
    messages.add(Message(
        'What\'s up?', DateTime.now().add(Duration(minutes: 5)), coworker));
    messages.add(Message(
        'Lunch?', DateTime.now().add(Duration(minutes: 10)), null,
        dataMimeType: 'image/png', dataUri: imageUri));
    messages.add(Message('What kind of food would you prefer?',
        DateTime.now().add(Duration(minutes: 10)), lunchBot));
    var messagingStyle = MessagingStyleInformation(me,
        groupConversation: true,
        conversationTitle: 'Team lunch',
        htmlFormatContent: true,
        htmlFormatTitle: true,
        messages: messages);
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'message channel id',
        'message channel name',
        'message channel description',
        category: 'msg',
        styleInformation: messagingStyle);
    var platformChannelSpecifics =
        NotificationDetails(androidPlatformChannelSpecifics, null);
    await flutterLocalNotificationsPlugin.show(
        0, 'message title', 'message body', platformChannelSpecifics);

    // wait 10 seconds and add another message to simulate another response
    await Future.delayed(Duration(seconds: 10), () async {
      messages.add(
          Message('Thai', DateTime.now().add(Duration(minutes: 11)), null));
      await flutterLocalNotificationsPlugin.show(
          0, 'message title', 'message body', platformChannelSpecifics);
    });
  }

  Future<void> _showGroupedNotifications() async {
    var groupKey = 'com.android.example.WORK_EMAIL';
    var groupChannelId = 'grouped channel id';
    var groupChannelName = 'grouped channel name';
    var groupChannelDescription = 'grouped channel description';
    // example based on https://developer.android.com/training/notify-user/group.html
    var firstNotificationAndroidSpecifics = AndroidNotificationDetails(
        groupChannelId, groupChannelName, groupChannelDescription,
        importance: Importance.Max,
        priority: Priority.High,
        groupKey: groupKey);
    var firstNotificationPlatformSpecifics =
        NotificationDetails(firstNotificationAndroidSpecifics, null);
    await flutterLocalNotificationsPlugin.show(1, 'Alex Faarborg',
        'You will not believe...', firstNotificationPlatformSpecifics);
    var secondNotificationAndroidSpecifics = AndroidNotificationDetails(
        groupChannelId, groupChannelName, groupChannelDescription,
        importance: Importance.Max,
        priority: Priority.High,
        groupKey: groupKey);
    var secondNotificationPlatformSpecifics =
        NotificationDetails(secondNotificationAndroidSpecifics, null);
    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).
    // this is required is regardless of which versions of Android your application is going to support
    var lines = List<String>();
    lines.add('Alex Faarborg  Check this out');
    lines.add('Jeff Chang    Launch Party');
    var inboxStyleInformation = InboxStyleInformation(lines,
        contentTitle: '2 messages', summaryText: 'janedoe@example.com');
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        groupChannelId, groupChannelName, groupChannelDescription,
        styleInformation: inboxStyleInformation,
        groupKey: groupKey,
        setAsGroupSummary: true);
    var platformChannelSpecifics =
        NotificationDetails(androidPlatformChannelSpecifics, null);
    await flutterLocalNotificationsPlugin.show(
        3, 'Attention', 'Two messages', platformChannelSpecifics);
  }

  Future<void> _checkPendingNotificationRequests() async {
    var pendingNotificationRequests =
        await flutterLocalNotificationsPlugin.pendingNotificationRequests();
    return showDialog<void>(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          content: Text(
              '${pendingNotificationRequests.length} pending notification requests'),
          actions: [
            FlatButton(
              child: Text('OK'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }

  Future<void> _cancelAllNotifications() async {
    await flutterLocalNotificationsPlugin.cancelAll();
  }

  Future<void> _showOngoingNotification() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your channel id', 'your channel name', 'your channel description',
        importance: Importance.Max,
        priority: Priority.High,
        ongoing: true,
        autoCancel: false);
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(0, 'ongoing notification title',
        'ongoing notification body', platformChannelSpecifics);
  }

  Future<void> _repeatNotification() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'repeating channel id',
        'repeating channel name',
        'repeating description');
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.periodicallyShow(0, 'repeating title',
        'repeating body', RepeatInterval.EveryMinute, platformChannelSpecifics);
  }

  Future<void> _showDailyAtTime() async {
    var time = Time(10, 0, 0);
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'repeatDailyAtTime channel id',
        'repeatDailyAtTime channel name',
        'repeatDailyAtTime description');
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.showDailyAtTime(
        0,
        'show daily title',
        'Daily notification shown at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
        time,
        platformChannelSpecifics);
  }

  Future<void> _showWeeklyAtDayAndTime() async {
    var time = Time(10, 0, 0);
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'show weekly channel id',
        'show weekly channel name',
        'show weekly description');
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(
        0,
        'show weekly title',
        'Weekly notification shown on Monday at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
        Day.Monday,
        time,
        platformChannelSpecifics);
  }

  Future<void> _showNotificationWithNoBadge() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'no badge channel', 'no badge name', 'no badge description',
        channelShowBadge: false,
        importance: Importance.Max,
        priority: Priority.High,
        onlyAlertOnce: true);
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0, 'no badge title', 'no badge body', platformChannelSpecifics,
        payload: 'item x');
  }

  Future<void> _showProgressNotification() async {
    var maxProgress = 5;
    for (var i = 0; i <= maxProgress; i++) {
      await Future.delayed(Duration(seconds: 1), () async {
        var androidPlatformChannelSpecifics = AndroidNotificationDetails(
            'progress channel',
            'progress channel',
            'progress channel description',
            channelShowBadge: false,
            importance: Importance.Max,
            priority: Priority.High,
            onlyAlertOnce: true,
            showProgress: true,
            maxProgress: maxProgress,
            progress: i);
        var iOSPlatformChannelSpecifics = IOSNotificationDetails();
        var platformChannelSpecifics = NotificationDetails(
            androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
        await flutterLocalNotificationsPlugin.show(
            0,
            'progress notification title',
            'progress notification body',
            platformChannelSpecifics,
            payload: 'item x');
      });
    }
  }

  Future<void> _showIndeterminateProgressNotification() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'indeterminate progress channel',
        'indeterminate progress channel',
        'indeterminate progress channel description',
        channelShowBadge: false,
        importance: Importance.Max,
        priority: Priority.High,
        onlyAlertOnce: true,
        showProgress: true,
        indeterminate: true);
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0,
        'indeterminate progress notification title',
        'indeterminate progress notification body',
        platformChannelSpecifics,
        payload: 'item x');
  }

  Future<void> _showNotificationWithUpdatedChannelDescription() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your channel id',
        'your channel name',
        'your updated channel description',
        importance: Importance.Max,
        priority: Priority.High,
        channelAction: AndroidNotificationChannelAction.Update);
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0,
        'updated notification channel',
        'check settings to see updated channel description',
        platformChannelSpecifics,
        payload: 'item x');
  }

  Future<void> _showPublicNotification() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your channel id', 'your channel name', 'your channel description',
        importance: Importance.Max,
        priority: Priority.High,
        ticker: 'ticker',
        visibility: NotificationVisibility.Public);
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(0, 'public notification title',
        'public notification body', platformChannelSpecifics,
        payload: 'item x');
  }

  Future<void> _showNotificationWithIconBadge() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'icon badge channel', 'icon badge name', 'icon badge description');
    var iOSPlatformChannelSpecifics = IOSNotificationDetails(badgeNumber: 1);
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0, 'icon badge title', 'icon badge body', platformChannelSpecifics,
        payload: 'item x');
  }

  Future<void> _showNotificationWithoutTimestamp() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your channel id', 'your channel name', 'your channel description',
        importance: Importance.Max, priority: Priority.High, showWhen: false);
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0, 'plain title', 'plain body', platformChannelSpecifics,
        payload: 'item x');
  }

  Future<void> _showNotificationWithCustomTimestamp() async {
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
      'your channel id',
      'your channel name',
      'your channel description',
      importance: Importance.Max,
      priority: Priority.High,
      showWhen: true,
      when: DateTime.now().millisecondsSinceEpoch - 120 * 1000,
    );
    var iOSPlatformChannelSpecifics = IOSNotificationDetails();
    var platformChannelSpecifics = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0, 'plain title', 'plain body', platformChannelSpecifics,
        payload: 'item x');
  }

  Future<void> _showNotificationWithAttachment() async {
    var bigPicturePath = await _downloadAndSaveFile(
        'http://via.placeholder.com/600x200', 'bigPicture.jpg');
    var iOSPlatformChannelSpecifics = IOSNotificationDetails(
        attachments: [IOSNotificationAttachment(bigPicturePath)]);
    var bigPictureAndroidStyle =
        BigPictureStyleInformation(FilePathAndroidBitmap(bigPicturePath));
    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
        'your channel id', 'your channel name', 'your channel description',
        importance: Importance.High,
        priority: Priority.High,
        styleInformation: bigPictureAndroidStyle);
    var notificationDetails = NotificationDetails(
        androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
        0,
        'notification with attachment title',
        'notification with attachment body',
        notificationDetails);
  }

  Future<void> _createNotificationChannel() async {
    var androidNotificationChannel = AndroidNotificationChannel(
      'your channel id 2',
      'your channel name 2',
      'your channel description 2',
    );
    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(androidNotificationChannel);

    await showDialog<void>(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
            content: Text(
                'Channel with name \"${androidNotificationChannel.name}\" created'),
            actions: [
              FlatButton(
                child: Text('OK'),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          );
        });
  }

  Future<void> _deleteNotificationChannel() async {
    const channelId = 'your channel id 2';
    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.deleteNotificationChannel(channelId);

    await showDialog<void>(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
            content: Text('Channel with id \"$channelId\" deleted'),
            actions: [
              FlatButton(
                child: Text('OK'),
                onPressed: () {
                  Navigator.of(context).pop();
                },
              ),
            ],
          );
        });
  }

  String _toTwoDigitString(int value) {
    return value.toString().padLeft(2, '0');
  }
}

class SecondScreen extends StatefulWidget {
  SecondScreen(this.payload);

  final String payload;

  @override
  State<StatefulWidget> createState() => SecondScreenState();
}

class SecondScreenState extends State<SecondScreen> {
  String _payload;
  @override
  void initState() {
    super.initState();
    _payload = widget.payload;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen with payload: ${(_payload ?? '')}'),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: Text('Go back!'),
        ),
      ),
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  flutter_local_notifications: ^1.4.3

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:flutter_local_notifications/flutter_local_notifications.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
99
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
99
Learn more about scoring.

We analyzed this package on May 24, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.1
  • pana: 0.13.8-dev
  • Flutter: 1.17.0

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.0.0-dev.28.0 <3.0.0
flutter 0.0.0
flutter_local_notifications_platform_interface ^1.0.1 1.0.1
platform ^2.2.1 2.2.1
Transitive dependencies
collection 1.14.12
meta 1.1.8
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
e2e ^0.2.4+2
flutter_driver
flutter_test
mockito ^4.1.1
pedantic ^1.8.0
plugin_platform_interface ^1.0.1 1.0.2