diff --git a/.vscode/settings.json b/.vscode/settings.json index 10b1f2e..ab192f1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,6 @@ "titleBar.activeBackground": "#4E3C31", "titleBar.activeForeground": "#FCFCFB" }, - "dart.flutterSdkPath": ".fvm/versions/3.24.3" + "dart.flutterSdkPath": ".fvm/versions/3.24.3", + "dart.lineLength": 80 } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2df9112..10cc788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,34 @@ -## [2.0.0] - 23/09/2024 +# [2.0.2+1] + +* Updated dependency to latest release +* Fix dart static analysis warnings + +# [2.0.2] + +* Added web documentation in README +* Updated SDK constraints + +# [2.0.1+2] + +* Update API reference link in README + +# [2.0.1+1] + +* Updated package description + +# [2.0.1] + +* Default android notification channel id is now "default" instead of "Notifications" +* Updated example app with more custom sounds +* Updated README.md +* Updated package description and added additional tags + +# [2.0.0+1] + +* Formatted files +* Updated scripts in pubspec.yaml + +# [2.0.0] * **BREAKING:** No navigator key param in handler, and in callbacks onTap, onOpenNotificationArrive * **BREAKING:** No context in callbacks onFcmTokenInitialize, onFcmTokenUpdate @@ -29,17 +59,17 @@ * Added issue tracker link * Updated README.md and badges -## [1.1.0] - 25/10/2022 +# [1.1.0] * Updated example app * Updated documentation * Updated some dependencies to latest release -## [1.0.9] - 04/08/2022 +# [1.0.9] * Updated dependencies -## [1.0.8] - 07/06/2022 +# [1.0.8] * Updated dependencies * Fixed linter warnings @@ -47,85 +77,85 @@ * Exported firebase_messaging package * Updated README.md -## [1.0.7] - 02/05/2022 +# [1.0.7] * Updated example app to a fully functional app... * Updated dependencies * Updated README.md -## [1.0.6] - 15/03/2022 +# [1.0.6] * Fixed sendNotification bug -## [1.0.5] - 15/03/2022 +# [1.0.5] * Fixed null check error in main widget. * Updated README.md -## [1.0.4] - 15/03/2022 +# [1.0.4] * Updated payload type. * Added notificationMeta in sendNotification. -## [1.0.3] - 15/03/2022 +# [1.0.3] * Minor fix in sendNotification. * Updated example app * Updated README.md -## [1.0.2] - 14/03/2022 +# [1.0.2] * Added sendNotification function to trigger FCM notification. * Updated dependencies * Updated README.md -## [1.0.1] - 26/01/2022 +# [1.0.1] * Updated license * Updated README.md -## [1.0.0] - 26/01/2022 +# [1.0.0] * Added linter and updated code accordingly * Updated example app * Updated dependencies * Updated README.md -## [0.0.8] - 28/10/2021 +# [0.0.8] * Exposed initializeFCMToken & onFCMTokenRefresh callbacks * Updated dependencies * Updated example app * Updated README.md -## [0.0.7] - 01/08/2021 +# [0.0.7] * Updated README.md -## [0.0.6] - 01/08/2021 +# [0.0.6] * Updated README.md -## [0.0.5] - 01/08/2021 +# [0.0.5] * Added custom sound support for iOS * Added images support in notifications * Updated dependencies * Updated README.md -## [0.0.4] - 04/07/2021 +# [0.0.4] * Updated README.md -## [0.0.3] - 30/06/2021 +# [0.0.3] * Bug fix -## [0.0.2] - 30/06/2021 +# [0.0.2] * Updated README.md -## [0.0.1] - 30/06/2021 +# [0.0.1] * Simple notifications handler which provides callbacks like onTap which really make it easy to handle notification taps and a lot more. diff --git a/README.md b/README.md index ed09b22..559bd2c 100644 --- a/README.md +++ b/README.md @@ -6,66 +6,171 @@ [![code size](https://img.shields.io/github/languages/code-size/rithik-dev/firebase_notifications_handler)](https://github.com/rithik-dev/firebase_notifications_handler) [![license MIT](https://img.shields.io/badge/license-MIT-purple.svg)](https://opensource.org/licenses/MIT) -* Simple notifications handler which provides callbacks like onTap which really make it easy to handle notification taps and a lot more. -* The package already handles local notifications so notifications are rendered in every case without any additional setup, however overrides are available if required. - -## Screenshots -           - - -## Migration Guide from v1.x to v2.x+ ([Full Changelog](https://github.com/rithik-dev/firebase_notifications_handler/blob/master/CHANGELOG.md#200---23092024)) - -* Numerous parameters were renamed to add clarity and consistency, and some were removed. Refer to the [CHANGELOG.md](https://github.com/rithik-dev/firebase_notifications_handler/blob/master/CHANGELOG.md#200---18032023) for more details. -* Removed Constants class and added LocalNotificationsConfiguration class which holds android and ios specific configs for local notifications, and takes default values from firebase message, but these parameters can be overwritten by passing in values in the function getters. -* NavigatorKey is no longer accepted/provided in the onTap, onOpenNotificationArrive callbacks. Instead, you'll have to create a key and maintain it in your app. Refer to the [example app](https://github.com/rithik-dev/firebase_notifications_handler/tree/master/example). -* Moved android-specific local notifications config params like channelId, channelName, sound etc. from Constants to localNotificationsConfiguration.androidConfig. -* Moved ios-specific local notifications config params like sound etc. to localNotificationsConfiguration.iosConfig. -* onFCMTokenRefresh is removed. Instead, you can use onFcmTokenUpdate callback. You can always maintain your own stream for tokens in your app if needed. -* NotificationTapDetails class is now called NotificationInfo, and NotificationInfo now also holds the firebase message as a parameter. -* onOpenNotificationArrive now provides an object of NotificationInfo instead of just the payload. The payload can be accessed simply by using `payload` property of this class. -* notificationArrivesSubscription now returns a Stream of NotificationInfo objects instead of just the payload. -* notificationIdGetter moved to localNotificationsConfiguration.notificationIdGetter - -## Getting Started -Step 1: Before you can add Firebase to your app, you need to create a Firebase project to connect to your application. -Visit [`Understand Firebase Projects`](https://firebase.google.com/docs/projects/learn-more) to learn more about Firebase projects. - -Step 2: To use Firebase in your app, you need to register your app with your Firebase project. -Registering your app is often called "adding" your app to your project. - -Also, register a web app if using on the web. -Follow on the screen instructions to initialize the project. +--- + +**FirebaseNotificationsHandler** is a simple and easy-to-use notifications handler for Firebase Notifications. It includes built-in support for local notifications, allowing your app to display notifications even when it's in the foreground with no extra setup. With customization options available, you can manage notification behavior seamlessly. + +The package uses a widget-based approach, and exposes a widget to handle the notifications. This makes it feel like home for Flutter developers, as it integrates seamlessly with Flutterโ€™s UI-driven architecture. With easy-to-use callbacks such as `onTap`, you can effortlessly manage and respond to notification taps and customize the notification behavior, providing a smooth integration process for any Flutter project. + +--- + +# ๐Ÿ—‚๏ธ Table of Contents + +- **[๐Ÿ“ท Screenshots](#-screenshots)** +- **[โœจ Features](#-features)** +- **[๐Ÿ›ซ Migration Guides](#-migration-guides)** + - [Migration Guide from v1.x to v2.x+](#migration-guide-from-v1x-to-v2x) +- **[๐Ÿš€ Getting Started](#-getting-started)** +- **[๐Ÿ› ๏ธ Platform-specific Setup](#%EF%B8%8F-platform-specific-setup)** + - [Android](#android) + - [iOS](#ios) + - [Web](#web) +- **[โ“ Usage](#-usage)** + - [Creating notification channels for Android](#creating-notification-channels-for-android) + - [Adding custom sound files in platform-specific folders](#adding-custom-sound-files-in-platform-specific-folders) + - [Android](#android-1) + - [iOS](#ios-1) +- **[๐Ÿ’ก Solutions to common issues](#-solutions-to-common-issues)** + - [Notification not showing as a pop up on Android device](#notification-not-showing-as-a-pop-up-on-android-device) + - [Custom sound not playing when notification received on Android device](#custom-sound-not-playing-when-notification-received-on-android-device) + - [Notification image not showing if app in background or terminated even when passed on Android device](#notification-image-not-showing-if-app-in-background-or-terminated-even-when-passed-on-android-device) + - [Custom sounds in Android work in debug mode but not in release mode](#custom-sounds-in-android-work-in-debug-mode-but-not-in-release-mode) +- **[๐ŸŽฏ Sample Usage](#-sample-usage)** +- **[๐Ÿ‘ค Collaborators](#-collaborators)** + +--- + +# ๐Ÿ“ท Screenshots + +| App In Foreground | App In Background | Expanded Notification | +|-----------------------------------|-------------------------------------|-------------------------------------| +| | | | + +--- + +# โœจ Features + +- **Foreground Notification Handling:** The package allows you to manage notifications even when the app is in the foreground, without needing additional setup. +- **Easy-to-use Callbacks:** You can define custom callbacks when a notification is tapped (`onTap`), when a notification arrives when app open (`onOpenNotificationArrive`), etc., making the widget simple to use. +- **Custom Sounds:** The package supports custom notification sounds for both Android and iOS platforms. +- **Automatic FCM Token Handling:** Built-in functionality to automatically handle Firebase Cloud Messaging (FCM) token initialization and updates, ensuring seamless management of push notification tokens. +- **Cross-Platform Support:** It provides full support for both Android and iOS, ensuring a consistent experience across platforms. +- **Widget-Based Approach:** The package integrates well with Flutterโ€™s UI-driven architecture, offering a widget-based solution for handling notifications. +- **Stream Subscription for Notifications:** Exposes various streams like `notificationTapsSubscription`, `notificationArrivesSubscription` allowing listening to important notification events and managing them easily with the provided NotificationInfo objects. +- **Deep Customization:** The package offers flexibility in customizing notification behavior, such as controlling which notifications are handled, defining custom actions for specific notifications, and setting platform-specific parameters for local notifications. +- **Solutions for Common Issues:** The README provides troubleshooting tips and solutions for commonly faced issues, such as notifications not appearing as pop-ups, custom sounds not working in release mode, and image handling limitations. + +--- + +# ๐Ÿ›ซ Migration Guides + +## Migration Guide from v1.x to v2.x+ + +### 1. Renaming of Parameters and Callbacks +Several parameters and callbacks were renamed for clarity and consistency: +- `NotificationTapDetails` is now `NotificationInfo`. +- `onFCMTokenInitialize` is now `onFcmTokenInitialize`. +- `onFCMTokenUpdate` is now `onFcmTokenUpdate`. +- `initializeFCMToken` is now `initializeFcmToken`. +- `requestPermissionsOnInit` is now `requestPermissionsOnInitialize`. +- `AppState.closed` is now `AppState.terminated`. + +### 2. Context and Navigator Key Removal +- **Navigator Key**: The `navigatorKey` parameter is no longer available in `onTap` and `onOpenNotificationArrive`. Youโ€™ll need to manage your own navigator key in your app. See the [example](https://github.com/rithik-dev/firebase_notifications_handler/blob/master/example) app for more details on handling navigation. +- **Context**: Callbacks such as `onFcmTokenInitialize` and `onFcmTokenUpdate` no longer accept `context`. Ensure that any context-dependent logic is refactored. + +### 3. Handling of Notifications +- **NotificationInfo**: The class `NotificationTapDetails` has been renamed to `NotificationInfo`. This class now includes the `firebaseMessage` parameter, providing more comprehensive information. +- `onTap` and `onOpenNotificationArrive` now return a `NotificationInfo` object, replacing the previous payload. The payload can still be accessed using the `payload` property of `NotificationInfo`. +- The `notificationArrivesSubscription` stream now returns `NotificationInfo` instead of just the payload. + +### 4. Local Notifications Configuration +The configuration of local notifications has been refactored to use platform-specific getters in `LocalNotificationsConfiguration`: +- Android-specific parameters like `channelId`, `channelName`, and `sound` have been moved to `localNotificationsConfiguration.androidConfig`. +- iOS-specific parameters like `sound` have been moved to `localNotificationsConfiguration.iosConfig`. +- The `notificationIdGetter` function is now also part of the `LocalNotificationsConfiguration`. + +### 5. FCM Token Changes +- The callback `onFCMTokenRefresh` has been removed. Use `onFcmTokenUpdate` instead to handle token updates. +- If you need to maintain your own stream of FCM tokens, you can do so manually in your app. + +### 6. Notification Sending +- **Removed**: `sendFcmNotification` has been deprecated for sending notifications from the client side. You'll now need to send notifications using Firebase Cloud Messaging (FCM) server-side APIs. +- **New**: A new `sendLocalNotification` function is introduced, which allows sending or scheduling local notifications. + +### 7. Other Notable Changes +- New streams `notificationTapsSubscription` and `notificationArrivesSubscription` are available for handling notification taps and arrivals. +- Android notification channel management methods have been added: create, read, and delete channels. +- New callbacks and getters such as `permissionGetter`, `shouldHandleNotification`, `messageModifier`, and `stateKeyGetter` are introduced for finer control over the notification lifecycle. +- Logging is now available in debug mode for better debugging. +- Fixed issues with images not displaying in notifications. +- `getInitialMessage` callback added for retrieving the initial notification that launched the app. + +### 8. Updated Example App and Documentation +- The [example](https://github.com/rithik-dev/firebase_notifications_handler/blob/master/example) app has been updated to use the latest SDKs and demonstrates how to implement these breaking changes. +- Documentation has been updated to reflect all changes, along with the issue tracker link for reporting bugs or issues. + +For more details, refer to the [CHANGELOG](https://github.com/rithik-dev/firebase_notifications_handler/blob/master/CHANGELOG.md#200). + +--- + +# ๐Ÿš€ Getting Started + +## Step 1: Create Firebase Project +Create a Firebase project. Learn more about Firebase projects [**here**](https://firebase.google.com/docs/projects/learn-more). + +## Step 2: Register your apps and configure Firebase +Add your Android & iOS apps to your Firebase project and configure the Firebase the apps by following the setup instructions for [Android](https://firebase.google.com/docs/flutter/setup?platform=android) and [iOS](https://firebase.google.com/docs/flutter/setup?platform=ios) separately. + +## Step 3: Add firebase_core dependency +Add [`firebase_core`](https://pub.dev/packages/firebase_core) as a dependency in your pubspec.yaml file. +```yaml +dependencies: + flutter: + sdk: flutter -Add the latest version 'firebase-messaging' CDN from [here](https://firebase.google.com/docs/web/setup#available-libraries) in index.html. -(Tested on version 8.6.1) + firebase_core: +``` - Step 3: Add a Firebase configuration file and the SDK's. (google-services) +## Step 4: Initialize Firebase +Call `Firebase.initializeApp()` in the `main()` method as shown to intialize Firebase in your project. - Step 4: Lastly, add [`firebase_core`](https://pub.dev/packages/firebase_core) as a dependency in your pubspec.yaml file. -and call `Firebase.initializeApp()` in the `main` method as shown: ```dart +import 'package:firebase_core/firebase_core.dart'; + void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(); - runApp(_MainApp()); + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + runApp(MyApp()); } ``` -### Android +--- -Add the default channel in AndroidManifest in the `` tag. Pass the same in the channelId parameter in the -`FirebaseNotificationsHandler` widget to enable custom sounds. +# ๐Ÿ› ๏ธ Platform-Specific Setup +## Android +> [!NOTE] +> Refer to the platform specific setup for local notifications [here](https://pub.dev/packages/flutter_local_notifications#-android-setup) +1. Add the following meta-data tags (if required) to define default values in `AndroidManifest.xml` under the `` tag: ```xml + + -``` -The `android:value` should be the same as the channel id in FirebaseNotificationsHandler. -The default value for channel id is "Notifications". + android:value="default" /> + + +``` + +2. Add this `` in the `` tag: ```xml @@ -73,11 +178,14 @@ Also, add this intent-filter in AndroidManifest in the `` tag with `an ``` -### Web -Provide the vapidKey in FirebaseNotificationsHandler from the cloud messaging settings by generating -a new Web push certificate +## iOS +> [!NOTE] +> Refer to the platform specific setup for local notifications [here](https://pub.dev/packages/flutter_local_notifications#-ios-setup) + +## Web +1. Provide the vapidKey in `FirebaseNotificationsHandler` from the cloud messaging settings by generating a new Web push certificate. -Add this script tag in index.html after adding the firebase config script +2. Add this script tag in `index.html` after adding the firebase config script ```html ``` -Now, finally create a file `firebase-messaging-sw.js` in the `web` folder itself -and paste the following contents. Add your own firebase app config here. - +3. Create a file `firebase-messaging-sw.js` in the `web` folder itself and paste the following contents. Add your own firebase app config here. ```js importScripts("https://www.gstatic.com/firebasejs/7.15.5/firebase-app.js"); importScripts("https://www.gstatic.com/firebasejs/7.15.5/firebase-messaging.js"); @@ -123,153 +229,182 @@ self.addEventListener('notificationclick', function (event) { }); ``` -## Custom Sound -#### Adding custom notification sounds in Android -- Add the audio file in android/app/src/main/res/raw/___audio_file_here___ -- Add the audio file name in the `soundGetter` parameter in the `AndroidConfig` class. - -Add a [keep.xml](https://github.com/rithik-dev/firebase_notifications_handler/tree/master/example/android/app/src/main/res/raw/keep.xml) file in the raw folder for Android, as during compilation, flutter strips off the raw folder, and the custom sounds won't work in release mode - -#### Adding custom notification sounds in iOS -- Add the audio file in Runner/Resources/___audio_file_here___ -- Add the audio file name in the `soundGetter` parameter in the `IosConfig` class. - - +--- -## Usage - -To use this plugin, add [`firebase_notifications_handler`](https://pub.dev/packages/firebase_notifications_handler) as a dependency in your pubspec.yaml file. +# โ“ Usage +1. Add [`firebase_notifications_handler`](https://pub.dev/packages/firebase_notifications_handler) as a dependency in your pubspec.yaml file. ```yaml - dependencies: - flutter: - sdk: flutter - firebase_notifications_handler: +dependencies: + flutter: + sdk: flutter + + firebase_notifications_handler: ``` -First and foremost, import the widget. +2. Wrap the `FirebaseNotificationsHandler` widget ideally as a parent widget on the `MaterialApp` to enable your application to receive notifications. ```dart import 'package:firebase_notifications_handler/firebase_notifications_handler.dart'; -``` - -Wrap the `FirebaseNotificationsHandler` on a widget to enable your application to receive notifications. -Typically wrap it on the screen, when you have all the initial setup done. (like on the home screen). -When the app launches, the splash screen typically loads all the stuff, initializes the users and -sends to the home screen, then the onTap will trigger, and can be handled accordingly from the callback. - -If wrapped on the material app, then you might push the user to the specified screen too early, -before initializing the user or something that you need. -```dart class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return FirebaseNotificationsHandler( - child: HomeScreen(), + child: MaterialApp(), ); } } ``` -Disabling logs: You can set the `enableLogs` parameter to false to disable the logs. -```dart -FirebaseNotificationsHandler.enableLogs = false; -``` - -Although, the widget automatically initializes the fcmToken, but if the FCM token is needed before the widget is built, -then use the `initializeFcmToken()` function to initialize the token. Which will return the initialized token. - -Also, keep in mind, when the widget is built, the onFCMTokenInitialize callback will also fire, with the same token. +Although, the widget automatically initializes the FCM token, but if the FCM token is needed before the widget is built, use the `FirebaseNotificationsHandler.initializeFcmToken()` function to initialize the token, which will initialize and return initialized token. This will also trigger the `onFCMTokenInitialize` callback. -There are multiple parameters that can be passed to the widget, some of them are shown. +3. Explore the widget [documentation](https://pub.dev/documentation/firebase_notifications_handler/latest/firebase_notifications_handler/FirebaseNotificationsHandler-class.html), and see the different configurations available, allowing you to customize each and every bit of your notifications. ```dart FirebaseNotificationsHandler( - onFcmTokenInitialize: (token) => fcmToken = token, - onFcmTokenUpdate: (token) { - fcmToken = token; - // await User.updateFCM(token); - }, - onTap: (details) { - final appState = details.appState; - final payload = details.payload; - - print("Notification tapped with $appState & payload $payload"); - - final context = Globals.navigatorState.currentContext!; - Globals.navigatorState.currentState!.pushNamed('newRouteName'); - // OR - Navigator.pushNamed(context, 'newRouteName'); - }, - localNotificationsConfiguration: LocalNotificationsConfiguration( - androidConfig: AndroidNotificationsConfig( - channelIdGetter: (msg) => msg.notification?.android?.channelId ?? 'default', - ), + localNotificationsConfiguration: LocalNotificationsConfiguration( + androidConfig: AndroidNotificationsConfig( + // ... ), - // ... and a lot more -), -``` - -You can check the remaining parameters [here](https://github.com/rithik-dev/firebase_notifications_handler/blob/master/lib/src/widget.dart). -They are fully documented and won't face an issue while using them - -## Steps to test the example app - -To test the example app, clone the project and replace the [firebase_options.dart](https://github.com/rithik-dev/firebase_notifications_handler/blob/master/example/lib/firebase_options.dart) with your firebase project. + iosConfig: IosNotificationsConfig( + // ... + ), + ), + onOpenNotificationArrive: (info) { + log( + id, + msg: 'Notification received while app is open with payload ${info.payload}', + ); + }, + onTap: (info) { + final payload = info.payload; + final appState = info.appState; + final firebaseMessage = info.firebaseMessage; + + // If you want to push a screen on notification tap + // + // Globals.navigatorKey.currentState?.pushNamed(payload['screenId']); + // + // OR + /// + // Get current context + // final context = Globals.navigatorKey.currentContext!; + + log( + id, + msg: 'Notification tapped with $appState & payload $payload. Firebase message: $firebaseMessage', + ); + }, + onFcmTokenInitialize: (token) => Globals.fcmTokenNotifier.value = token, + onFcmTokenUpdate: (token) => Globals.fcmTokenNotifier.value = token, + // ... +); +``` + +## Creating notification channels for Android +By default, if you send a notification, the device will automatically create a notification channel with the passed `channelId`, but the priority for that channel will be normal, and the notification will not show up as a popup. -Then, build an apk and run it. The app is set to receive notifications. +Creating a default channel lets you set the priorty to `high` and also give you more customization of the channels like setting a custom notification sound, setting vibration patterns etc. +```dart +FirebaseNotificationsHandler.createAndroidNotificationChannel( + const AndroidNotificationChannel( + 'marketing', + 'Marketing', + description: 'Notification channel for marketing', + playSound: true, + importance: Importance.max, + sound: RawResourceAndroidNotificationSound('marketing'), + ), +); +``` -### To Send Notifications +It is recommended to create notification channels as soon as the app starts, as the custom sounds will not play if the channel is not created for the first time, and it might cause issues with other parameters as well. +```dart +FirebaseNotificationsHandler.createAndroidNotificationChannels([ + const AndroidNotificationChannel( + 'promotions', + 'Promotions', + description: 'Notification channel for promotions', + playSound: true, + importance: Importance.max, + sound: RawResourceAndroidNotificationSound('chime'), + ), + const AndroidNotificationChannel( + 'order-updates', + 'Order Updates', + description: 'Notification channel for order updates', + playSound: true, + importance: Importance.max, + sound: RawResourceAndroidNotificationSound('elevator'), + ), + const AndroidNotificationChannel( + 'messages', + 'Messages', + description: 'Notification channel for messages', + playSound: true, + importance: Importance.max, + sound: RawResourceAndroidNotificationSound('bell'), + ), +]); +``` -#### Using Firebase Console -Open the [Firebase Console](https://console.firebase.google.com/), and then go to Build > Messaging from the left panel. Choose Create first campaign, and then Firebase notification message, -put in the title, body and image (if any), and then press Send test message, paste the FCM token which you can get by running the example app and copy it from there, and then send the notification. +## Adding custom sound files in platform-specific folders -#### Using Node Project -To send notifications using a node project, clone this [notification-sender](https://github.com/rithik-dev/notification-sender) project, +### Android +> [!IMPORTANT] +> Add a [keep.xml](https://github.com/rithik-dev/firebase_notifications_handler/tree/master/example/android/app/src/main/res/raw/keep.xml) file in the `android/app/src/main/res/raw/` folder, as flutter strips off the `raw` folder when compiling app in release mode, and hence the custom sounds won't work in release mode. -Download the service account key file by visiting the [Google Cloud Service Accounts Panel](https://console.cloud.google.com/iam-admin/serviceaccounts/) and select the correct project, and add a new key or use an existing one if you already have. +* Add the audio file in the `android/app/src/main/res/raw/` folder. -You should now have the project id, client email and the private key from the json key file, and create a .env file in the [root folder](https://github.com/rithik-dev/notification-sender/tree/main). Add keys `FIREBASE_PROJECT_ID`, `CLIENT_EMAIL` and `PRIVATE_KEY` in the .env file. +### iOS +- Add the audio file in the `ios/Runner/Resources/` folder. -Now copy the fcm token from the running example app, and pass it to the [index.ts](https://github.com/rithik-dev/notification-sender/blob/main/src/index.ts) file in the `fcm_tokens` array, -and run `npm start`. +--- -## Debugging common issues -#### Notification not showing as a pop up on Android device: -###### On Android devices, a notification channel by default when a notification arrives, but that might not have the priority set to high. The notification only shows up as a popup if the channel you're sending it to has priority set as "high". We can solve this issue by creating a notification channel on app start using: +# ๐Ÿ’ก Solutions to common issues +## Notification not showing as a pop up on Android device: +On Android devices, a notification channel by default when a notification arrives, but that might not have the priority set to high. The notification only shows up as a popup if the channel you're sending it to has priority set as "high". We can solve this issue by creating a notification channel on app start using: ```dart FirebaseNotificationsHandler.createAndroidNotificationChannel( const AndroidNotificationChannel( - 'Notifications', - 'Notifications', + 'default', + 'Default', importance: Importance.high, ), ); ``` -#### Custom sound not playing when notification received on Android device: -###### On Android devices, a notification channel by default when a notification arrives, but that won't have the sound set to it by default. The sound will only play if the channel was creating while specifying the custom sound you want to play for that channel. We can solve this issue by creating a notification channel on app start and passing in the sound using: +## Custom sound not playing when notification received on Android device: +On Android devices, a notification channel by default when a notification arrives, but that won't have the sound set to it by default. The sound will only play if the channel was creating while specifying the custom sound you want to play for that channel. +> [!NOTE] +> You cannot modify a channel's sound after it's created. Only way is to either use a new channel id or delete an existing channel using `FirebaseNotificationsHandler.deleteAndroidNotificationChannel(String channelId);` and creating a new one with the new sound. Or try uninstalling the app and creating the channel again. + +We can solve this issue by creating a notification channel on app start and passing in the sound using: ```dart FirebaseNotificationsHandler.createAndroidNotificationChannel( const AndroidNotificationChannel( - 'Notifications', - 'Notifications', + 'default', + 'Default', playSound: true, importance: Importance.high, - sound: RawResourceAndroidNotificationSound('custom_sound'), + sound: RawResourceAndroidNotificationSound('pop'), ), ); ``` -###### PS: You cannot modify a channel's sound after it's created. Only way is to either use a new channel id or delete an existing channel using `FirebaseNotificationsHandler.deleteAndroidNotificationChannel(String channelId);` and creating a new one with the new sound. Or try uninstalling the app and creating the channel again. +## Notification image not showing if app in background or terminated even when passed on Android device: +The max size for a notification to be displayed by firebase on an Android device is 1MB ([Source](https://firebase.google.com/docs/cloud-messaging/android/send-image#:~:text=Images%20for%20notifications%20are%20limited,by%20native%20Android%20image%20support.)). So, if an image exceeds this size, it is not shown in the notification. However, if the app is in foreground, then there is no size limitation as then it's handled by local notifications. + +## Custom sounds in Android work in debug mode but not in release mode: +Flutter strips off the `raw` folder during compiling build for release mode. We can add a file [keep.xml](https://github.com/rithik-dev/firebase_notifications_handler/tree/master/example/android/app/src/main/res/raw/keep.xml) in the raw folder, which tells flutter to not strip off the raw folder, and hence fixing the issue. + +--- -#### Notification image not showing if app in background or terminated even when passed on Android device: -###### The max size for a notification to be displayed by firebase on an Android device is 1MB ([Source](https://firebase.google.com/docs/cloud-messaging/android/send-image#:~:text=Images%20for%20notifications%20are%20limited,by%20native%20Android%20image%20support.)). So, if an image exceeds this size, it is not shown in the notification. However, if the app is in foreground, then there is no size limitation as then it's handled by local notifications. +# ๐ŸŽฏ Sample Usage -#### Custom sounds in Android work in debug mode but not in release mode -###### Flutter strips off the `raw` folder during compiling build for release mode. We can add a file [keep.xml](https://github.com/rithik-dev/firebase_notifications_handler/tree/master/example/android/app/src/main/res/raw/keep.xml) in the raw folder, which tells flutter to not strip off the raw folder, and hence fixing the issue. +See the [example](https://github.com/rithik-dev/firebase_notifications_handler/blob/master/example) app for a complete app. Learn how to setup the example app for testing [here](https://github.com/rithik-dev/firebase_notifications_handler/blob/master/example/README.md). + +Check out the full API reference of the widget [here](https://pub.dev/documentation/firebase_notifications_handler/latest/firebase_notifications_handler/FirebaseNotificationsHandler-class.html). -## Sample Usage ```dart import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_notifications_handler/firebase_notifications_handler.dart'; @@ -297,10 +432,10 @@ class _MainApp extends StatelessWidget { return FirebaseNotificationsHandler( localNotificationsConfiguration: LocalNotificationsConfiguration( androidConfig: AndroidNotificationsConfig( - channelIdGetter: (msg) => msg.notification?.android?.channelId ?? 'default', + // ... ), iosConfig: IosNotificationsConfig( - soundGetter: (_) => 'ios_sound.caf', + // ... ), ), shouldHandleNotification: (msg) { @@ -308,16 +443,15 @@ class _MainApp extends StatelessWidget { return true; }, onOpenNotificationArrive: (info) { - // final context = Globals.navigatorKey.currentContext!; - log( id, - msg: "Notification received while app is open with payload ${info.payload}", + msg: 'Notification received while app is open with payload ${info.payload}', ); }, onTap: (info) { final payload = info.payload; final appState = info.appState; + final firebaseMessage = info.firebaseMessage; /// If you want to push a screen on notification tap /// @@ -332,7 +466,7 @@ class _MainApp extends StatelessWidget { log( id, - msg: "Notification tapped with $appState & payload $payload", + msg: 'Notification tapped with $appState & payload $payload. Firebase message: $firebaseMessage', ); }, onFcmTokenInitialize: (token) => Globals.fcmTokenNotifier.value = token, @@ -352,9 +486,11 @@ class _MainApp extends StatelessWidget { } ``` -See the [`example`](https://github.com/rithik-dev/firebase_notifications_handler/blob/master/example) directory for a complete sample app. +--- + +# ๐Ÿ‘ค Collaborators -### Created & Maintained By `Rithik Bhandari` -* GitHub: [@rithik-dev](https://github.com/rithik-dev) -* LinkedIn: [@rithik-bhandari](https://www.linkedin.com/in/rithik-bhandari/) \ No newline at end of file +| Name | GitHub | Linkedin | +|-----------------------------------|-------------------------------------|-------------------------------------| +| Rithik Bhandari | [github/rithik-dev](https://github.com/rithik-dev) | [linkedin/rithik-bhandari](https://www.linkedin.com/in/rithik-bhandari) | \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore index 75dfa95..d255729 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -43,4 +43,6 @@ app.*.map.json /android/app/release # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ + +lib/firebase_options.dart \ No newline at end of file diff --git a/example/README.md b/example/README.md index 206d9d2..62ce4a6 100644 --- a/example/README.md +++ b/example/README.md @@ -1,16 +1,18 @@ -# notifications_handler_demo +# Firebase Notifications Handler Demo -A new Flutter project. +To test the example app, clone the project and add the `firebase_options.dart` with your firebase options file in `lib/firebase_options.dart`. -## Getting Started +Then, build the app and run it. The app is set to receive notifications. -This project is a starting point for a Flutter application. +## How to send notifications test notifications to the app -A few resources to get you started if this is your first Flutter project: +- ### Using Firebase Console + 1. Open the [Firebase Console](https://console.firebase.google.com/), and then go to Build > Messaging from the left panel. Choose Create first campaign, and then Firebase notification message. + 2. Set the title, body and image (if any), and then press Send test message, paste the FCM token which you can get by running the example app and copy it, and then send the notification. -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +- ### Using Node Project + 1. Clone this [notification-sender](https://github.com/rithik-dev/notification-sender) project, + 2. Download the service account key file by visiting the [Google Cloud Service Accounts Panel](https://console.cloud.google.com/iam-admin/serviceaccounts) and select the correct project, and add a new key or use an existing one if you already have. + 3. Create a `.env` file in the [root folder](https://github.com/rithik-dev/notification-sender/tree/main). Add keys `FIREBASE_PROJECT_ID`, `CLIENT_EMAIL` and `PRIVATE_KEY` in the .env file. + 4. Get the FCM token by running the example app, and pass it to the `fcm_tokens` property of `sendNotification` function in the [index.ts](https://github.com/rithik-dev/notification-sender/blob/main/src/index.ts) file. + 5. Run `npm start`. \ No newline at end of file diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index fef2901..b60a90a 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -9,7 +9,15 @@ + android:value="default" /> + + msg.notification?.android?.channelId ?? 'default', - ), + // ... + ), iosConfig: IosNotificationsConfig( - soundGetter: (_) => 'ios_sound.caf', - ), + // ... + ), ), shouldHandleNotification: (msg) { // add some logic and return bool on whether to handle a notif or not @@ -37,12 +37,14 @@ class _MainApp extends StatelessWidget { onOpenNotificationArrive: (info) { log( id, - msg: "Notification received while app is open with payload ${info.payload}", + msg: + 'Notification received while app is open with payload ${info.payload}', ); }, onTap: (info) { final payload = info.payload; final appState = info.appState; + final firebaseMessage = info.firebaseMessage; /// If you want to push a screen on notification tap /// @@ -57,11 +59,13 @@ class _MainApp extends StatelessWidget { log( id, - msg: "Notification tapped with $appState & payload $payload", + msg: + 'Notification tapped with $appState & payload $payload. Firebase message: $firebaseMessage', ); }, onFcmTokenInitialize: (token) => Globals.fcmTokenNotifier.value = token, onFcmTokenUpdate: (token) => Globals.fcmTokenNotifier.value = token, + // ... child: MaterialApp( debugShowCheckedModeBanner: false, title: 'FirebaseNotificationsHandler Demo', diff --git a/example/lib/screens/home_screen.dart b/example/lib/screens/home_screen.dart index a228145..b6fbf3e 100644 --- a/example/lib/screens/home_screen.dart +++ b/example/lib/screens/home_screen.dart @@ -21,7 +21,8 @@ class HomeScreen extends StatefulWidget { } class _HomeScreenState extends State { - static const _projectLink = 'https://pub.dev/packages/firebase_notifications_handler'; + static const _projectLink = + 'https://pub.dev/packages/firebase_notifications_handler'; final _notificationTaps = []; final _notificationArrives = []; @@ -45,21 +46,49 @@ class _HomeScreenState extends State { void initState() { super.initState(); - FirebaseNotificationsHandler.createAndroidNotificationChannel( + FirebaseNotificationsHandler.createAndroidNotificationChannels([ + // create default notification channel + AndroidNotificationChannel( + AndroidNotificationsConfig.defaultChannelId, + AndroidNotificationsConfig.defaultChannelName, + description: AndroidNotificationsConfig.defaultChannelDescription, + importance: Importance.max, + playSound: true, + sound: const RawResourceAndroidNotificationSound('pop'), + ), const AndroidNotificationChannel( - 'Notifications', - 'Notifications', + 'promotions', + 'Promotions', + description: 'Notification channel for promotions', playSound: true, importance: Importance.max, - sound: RawResourceAndroidNotificationSound('custom_sound'), + sound: RawResourceAndroidNotificationSound('chime'), ), - ); + const AndroidNotificationChannel( + 'order-updates', + 'Order Updates', + description: 'Notification channel for order updates', + playSound: true, + importance: Importance.max, + sound: RawResourceAndroidNotificationSound('elevator'), + ), + const AndroidNotificationChannel( + 'messages', + 'Messages', + description: 'Notification channel for messages', + playSound: true, + importance: Importance.max, + sound: RawResourceAndroidNotificationSound('bell'), + ), + ]); - _notificationTapsSubscription = - FirebaseNotificationsHandler.notificationTapsSubscription.listen(_addNotificationTap); + _notificationTapsSubscription = FirebaseNotificationsHandler + .notificationTapsSubscription + .listen(_addNotificationTap); - _notificationArriveSubscription = - FirebaseNotificationsHandler.notificationArrivesSubscription.listen(_addNotificationArrive); + _notificationArriveSubscription = FirebaseNotificationsHandler + .notificationArrivesSubscription + .listen(_addNotificationArrive); } @override @@ -96,7 +125,8 @@ class _HomeScreenState extends State { const SizedBox(width: 10), IconButton( onPressed: () async { - await Clipboard.setData(ClipboardData(text: value)); + await Clipboard.setData( + ClipboardData(text: value)); showSnackBar('FCM token copied to clipboard!'); }, icon: const Icon(Icons.copy), @@ -120,7 +150,8 @@ class _HomeScreenState extends State { TextSpan( text: _projectLink, style: const TextStyle(color: Colors.blue), - recognizer: _linkTapRecognizer..onTap = () => launchUrlString(_projectLink), + recognizer: _linkTapRecognizer + ..onTap = () => launchUrlString(_projectLink), ), const TextSpan(text: ' to see how to send notifications'), ], diff --git a/example/lib/utils/app_theme.dart b/example/lib/utils/app_theme.dart index 9024870..b38f233 100644 --- a/example/lib/utils/app_theme.dart +++ b/example/lib/utils/app_theme.dart @@ -85,7 +85,8 @@ class AppTheme { // ), pageTransitionsTheme: PageTransitionsTheme( builders: { - for (final targetValue in TargetPlatform.values) targetValue: const _SlideLeftTransitionsBuilder(), + for (final targetValue in TargetPlatform.values) + targetValue: const _SlideLeftTransitionsBuilder(), }, ), ); diff --git a/example/lib/utils/route_generator.dart b/example/lib/utils/route_generator.dart index 02bbd52..ebb12d2 100644 --- a/example/lib/utils/route_generator.dart +++ b/example/lib/utils/route_generator.dart @@ -19,7 +19,8 @@ class RouteGenerator { } } - static MaterialPageRoute _route(Widget widget) => MaterialPageRoute(builder: (context) => widget); + static MaterialPageRoute _route(Widget widget) => + MaterialPageRoute(builder: (context) => widget); static Route _errorRoute(String? name) { return MaterialPageRoute( diff --git a/example/pubspec.lock b/example/pubspec.lock index f14978e..fa8ebb5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -140,10 +140,10 @@ packages: firebase_notifications_handler: dependency: "direct main" description: - path: "/Users/rithikbhandari/Developer/AndroidStudioProjects/_Personal/_packages/firebase_notifications_handler" - relative: false + path: ".." + relative: true source: path - version: "1.1.0" + version: "2.0.2+1" flutter: dependency: "direct main" description: flutter @@ -161,26 +161,26 @@ packages: dependency: transitive description: name: flutter_local_notifications - sha256: "49eeef364fddb71515bc78d5a8c51435a68bccd6e4d68e25a942c5e47761ae71" + sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 url: "https://pub.dev" source: hosted - version: "17.2.3" + version: "18.0.1" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af + sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "5.0.0" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "8.0.0" flutter_test: dependency: transitive description: flutter diff --git a/lib/firebase_notifications_handler.dart b/lib/firebase_notifications_handler.dart index 28c50e0..48dde81 100644 --- a/lib/firebase_notifications_handler.dart +++ b/lib/firebase_notifications_handler.dart @@ -1,4 +1,4 @@ -library firebase_notifications_handler; +library; export 'package:firebase_messaging/firebase_messaging.dart'; export 'package:flutter_local_notifications/flutter_local_notifications.dart'; diff --git a/lib/src/enums/app_state.dart b/lib/src/enums/app_state.dart index 9aa96e7..e0e6316 100644 --- a/lib/src/enums/app_state.dart +++ b/lib/src/enums/app_state.dart @@ -1,4 +1,4 @@ -/// This enum defines [AppState], i.e. the current state of the app. +/// This enum defines [AppState], i.e. the state of the app. enum AppState { /// [open] means that the app is in the foreground, i.e. currently open. open, diff --git a/lib/src/models/local_notifications_config.dart/android_config.dart b/lib/src/models/local_notifications_config.dart/android_config.dart index 92b5b61..a4ef894 100644 --- a/lib/src/models/local_notifications_config.dart/android_config.dart +++ b/lib/src/models/local_notifications_config.dart/android_config.dart @@ -23,25 +23,31 @@ class AndroidNotificationsConfig { BoolGetter? enableLightsGetter, BoolGetter? enableVibrationGetter, }) { - this.channelIdGetter = - channelIdGetter ?? (msg) => msg.notification?.android?.channelId ?? defaultChannelId; + this.channelIdGetter = channelIdGetter ?? + (msg) => msg.notification?.android?.channelId ?? defaultChannelId; this.channelNameGetter = channelNameGetter ?? (_) => defaultChannelName; - this.channelDescriptionGetter = channelDescriptionGetter ?? (_) => defaultChannelDescription; + this.channelDescriptionGetter = + channelDescriptionGetter ?? (_) => defaultChannelDescription; this.appIconGetter = appIconGetter ?? (_) => defaultAppIcon; this.colorGetter = colorGetter ?? (_) => defaultColor; this.groupKeyGetter = groupKeyGetter ?? (_) => defaultGroupKey; - this.tagGetter = tagGetter ?? (msg) => msg.notification?.android?.tag ?? defaultTag; - this.smallIconUrlGetter = - smallIconUrlGetter ?? (msg) => msg.notification?.android?.smallIcon ?? defaultSmallIcon; + this.tagGetter = + tagGetter ?? (msg) => msg.notification?.android?.tag ?? defaultTag; + this.smallIconUrlGetter = smallIconUrlGetter ?? + (msg) => msg.notification?.android?.smallIcon ?? defaultSmallIcon; this.importanceGetter = importanceGetter ?? (_) => defaultImportance; this.priorityGetter = priorityGetter ?? (_) => defaultPriority; - this.soundGetter = soundGetter ?? (msg) => msg.notification?.android?.sound ?? defaultSound; + this.soundGetter = soundGetter ?? + (msg) => msg.notification?.android?.sound ?? defaultSound; this.iconGetter = iconGetter ?? (_) => defaultIcon; - this.imageUrlGetter = imageUrlGetter ?? (msg) => msg.notification?.android?.imageUrl ?? defaultImageUrl; - this.hideExpandedLargeIconGetter = hideExpandedLargeIconGetter ?? (_) => defaultHideExpandedLargeIcon; + this.imageUrlGetter = imageUrlGetter ?? + (msg) => msg.notification?.android?.imageUrl ?? defaultImageUrl; + this.hideExpandedLargeIconGetter = + hideExpandedLargeIconGetter ?? (_) => defaultHideExpandedLargeIcon; this.playSoundGetter = playSoundGetter ?? (_) => defaultPlaySound; this.enableLightsGetter = enableLightsGetter ?? (_) => defaultEnableLights; - this.enableVibrationGetter = enableVibrationGetter ?? (_) => defaultEnableVibration; + this.enableVibrationGetter = + enableVibrationGetter ?? (_) => defaultEnableVibration; } /// {@template channelIdGetter} @@ -50,30 +56,31 @@ class AndroidNotificationsConfig { /// then it is used, if not then the default value is used, else the value /// passed will be used. /// - /// The notification channel's id. Defaults to 'Notifications'. + /// The notification channel's id. Defaults to 'default'. /// /// Required for Android 8.0 or newer. /// /// {@endtemplate} - static String defaultChannelId = 'Notifications'; + static String defaultChannelId = 'default'; /// {@template channelNameGetter} /// - /// The notification channel's name. Defaults to 'Notifications'. + /// The notification channel's name. Defaults to 'Default'. /// /// Required for Android 8.0 or newer. /// /// {@endtemplate} - static String defaultChannelName = 'Notifications'; + static String defaultChannelName = 'Default'; /// {@template channelDescriptionGetter} /// - /// The notification channel's description. Defaults to 'Notifications'. + /// The notification channel's description. Defaults to 'Default channel for all notifications'. /// /// Required for Android 8.0 or newer. /// /// {@endtemplate} - static String defaultChannelDescription = 'Notifications'; + static String defaultChannelDescription = + 'Default channel for all notifications'; /// {@template appIconGetter} /// @@ -285,7 +292,9 @@ class AndroidNotificationsConfig { tag: tagGetter(message), priority: priorityGetter(message), groupKey: groupKeyGetter(message), - sound: androidSound == null ? null : RawResourceAndroidNotificationSound(androidSound), + sound: androidSound == null + ? null + : RawResourceAndroidNotificationSound(androidSound), icon: iconGetter(message), playSound: playSoundGetter(message), enableLights: enableLightsGetter(message), diff --git a/lib/src/models/local_notifications_config.dart/ios_config.dart b/lib/src/models/local_notifications_config.dart/ios_config.dart index 174bd9c..9887544 100644 --- a/lib/src/models/local_notifications_config.dart/ios_config.dart +++ b/lib/src/models/local_notifications_config.dart/ios_config.dart @@ -19,7 +19,8 @@ class IosNotificationsConfig { BoolGetter? hideThumbnailGetter, IosNotificationAttachmentClippingRectGetter? thumbnailClippingRectGetter, }) { - final soundGetterRef = soundGetter ?? (msg) => msg.notification?.apple?.sound?.name ?? defaultSound; + final soundGetterRef = soundGetter ?? + (msg) => msg.notification?.apple?.sound?.name ?? defaultSound; this.soundGetter = (msg) { final sound = soundGetterRef(msg); @@ -34,19 +35,26 @@ class IosNotificationsConfig { return sound; }; - this.subtitleGetter = subtitleGetter ?? (msg) => msg.notification?.apple?.subtitle ?? defaultSubtitle; - this.imageUrlGetter = imageUrlGetter ?? (msg) => msg.notification?.apple?.imageUrl ?? defaultImageUrl; + this.subtitleGetter = subtitleGetter ?? + (msg) => msg.notification?.apple?.subtitle ?? defaultSubtitle; + this.imageUrlGetter = imageUrlGetter ?? + (msg) => msg.notification?.apple?.imageUrl ?? defaultImageUrl; this.badgeNumberGetter = badgeNumberGetter ?? (_) => defaultBadgeNumber; - this.categoryIdentifierGetter = categoryIdentifierGetter ?? (_) => defaultCategoryIdentifier; - this.threadIdentifierGetter = threadIdentifierGetter ?? (_) => defaultThreadIdentifier; - this.interruptionLevelGetter = interruptionLevelGetter ?? (_) => defaultInterruptionLevel; - this.hideThumbnailGetter = hideThumbnailGetter ?? (_) => defaultHideThumbnail; - this.thumbnailClippingRectGetter = - thumbnailClippingRectGetter ?? (_) => defaultThumbnailClippingRectGetter; + this.categoryIdentifierGetter = + categoryIdentifierGetter ?? (_) => defaultCategoryIdentifier; + this.threadIdentifierGetter = + threadIdentifierGetter ?? (_) => defaultThreadIdentifier; + this.interruptionLevelGetter = + interruptionLevelGetter ?? (_) => defaultInterruptionLevel; + this.hideThumbnailGetter = + hideThumbnailGetter ?? (_) => defaultHideThumbnail; + this.thumbnailClippingRectGetter = thumbnailClippingRectGetter ?? + (_) => defaultThumbnailClippingRectGetter; this.presentSoundGetter = presentSoundGetter ?? (_) => defaultPresentSound; this.presentAlertGetter = presentAlertGetter ?? (_) => defaultPresentAlert; this.presentBadgeGetter = presentBadgeGetter ?? (_) => defaultPresentBadge; - this.presentBannerGetter = presentBannerGetter ?? (_) => defaultPresentBanner; + this.presentBannerGetter = + presentBannerGetter ?? (_) => defaultPresentBanner; this.presentListGetter = presentListGetter ?? (_) => defaultPresentList; } @@ -239,7 +247,8 @@ class IosNotificationsConfig { /// The clipping rectangle for the thumbnail image. /// /// {@endtemplate} - static DarwinNotificationAttachmentThumbnailClippingRect? defaultThumbnailClippingRectGetter; + static DarwinNotificationAttachmentThumbnailClippingRect? + defaultThumbnailClippingRectGetter; /// {@macro soundGetter} late NullableStringGetter soundGetter; diff --git a/lib/src/utils/types.dart b/lib/src/utils/types.dart index 34ecb0f..cafccb5 100644 --- a/lib/src/utils/types.dart +++ b/lib/src/utils/types.dart @@ -22,5 +22,6 @@ typedef AndroidImportanceGetter = Importance Function(RemoteMessage); typedef AndroidPriorityGetter = Priority Function(RemoteMessage); typedef IosInterruptionLevelGetter = InterruptionLevel? Function(RemoteMessage); -typedef IosNotificationAttachmentClippingRectGetter = DarwinNotificationAttachmentThumbnailClippingRect? - Function(RemoteMessage); +typedef IosNotificationAttachmentClippingRectGetter + = DarwinNotificationAttachmentThumbnailClippingRect? Function( + RemoteMessage); diff --git a/lib/src/widget.dart b/lib/src/widget.dart index 87006e9..ad952d5 100644 --- a/lib/src/widget.dart +++ b/lib/src/widget.dart @@ -41,7 +41,8 @@ class FirebaseNotificationsHandler extends StatefulWidget { /// opened from a notification. /// /// {@endtemplate} - static final openedAppFromNotification = _FirebaseNotificationsHandlerState._openedAppFromNotification; + static final openedAppFromNotification = + _FirebaseNotificationsHandlerState._openedAppFromNotification; /// {@template vapidKey} /// @@ -190,26 +191,31 @@ class FirebaseNotificationsHandler extends StatefulWidget { this.handleInitialMessage = true, this.requestPermissionsOnInitialize = true, this.permissionGetter, - this.localNotificationsConfiguration = const LocalNotificationsConfiguration(), + this.localNotificationsConfiguration = + const LocalNotificationsConfiguration(), required this.child, }); - static void setOnTap(OnTapGetter? onTap) => _FirebaseNotificationsHandlerState._onTap = onTap; + static void setOnTap(OnTapGetter? onTap) => + _FirebaseNotificationsHandlerState._onTap = onTap; static void setOnOpenNotificationArrive( OnOpenNotificationArrive? onOpenNotificationArrive, ) => - _FirebaseNotificationsHandlerState._onOpenNotificationArrive = onOpenNotificationArrive; + _FirebaseNotificationsHandlerState._onOpenNotificationArrive = + onOpenNotificationArrive; static void setShouldHandleNotification( BoolGetter? shouldHandleNotification, ) => - _FirebaseNotificationsHandlerState._shouldHandleNotification = shouldHandleNotification; + _FirebaseNotificationsHandlerState._shouldHandleNotification = + shouldHandleNotification; static void setOnFcmTokenInitialize( FcmInitializeGetter? onFcmTokenInitialize, ) => - _FirebaseNotificationsHandlerState._onFCMTokenInitialize = onFcmTokenInitialize; + _FirebaseNotificationsHandlerState._onFCMTokenInitialize = + onFcmTokenInitialize; static void setOnFcmTokenUpdate( FcmUpdateGetter? onFcmTokenUpdate, @@ -219,7 +225,8 @@ class FirebaseNotificationsHandler extends StatefulWidget { static void setNotificationIdGetter( NotificationIdGetter? notificationIdGetter, ) => - _FirebaseNotificationsHandlerState._notificationIdGetter = notificationIdGetter; + _FirebaseNotificationsHandlerState._notificationIdGetter = + notificationIdGetter; static void setMessageModifier( RemoteMessageGetter? messageModifier, @@ -250,21 +257,24 @@ class FirebaseNotificationsHandler extends StatefulWidget { /// Request permission to show notifications. /// /// {@endtemplate} - static final requestPermission = _FirebaseNotificationsHandlerState._fcm.requestPermission; + static final requestPermission = + _FirebaseNotificationsHandlerState._fcm.requestPermission; /// {@template initializeFcmToken} /// /// Initialize the FCM token. /// /// {@endtemplate} - static const initializeFcmToken = _FirebaseNotificationsHandlerState.initializeFcmToken; + static const initializeFcmToken = + _FirebaseNotificationsHandlerState.initializeFcmToken; /// {@template sendLocalNotification} /// /// Send/schedule local notification. /// /// {@endtemplate} - static const sendLocalNotification = _FirebaseNotificationsHandlerState.sendLocalNotification; + static const sendLocalNotification = + _FirebaseNotificationsHandlerState.sendLocalNotification; /// Creates a notification channel. /// @@ -276,7 +286,8 @@ class FirebaseNotificationsHandler extends StatefulWidget { /// /// This method is only applicable to Android versions 8.0 or newer. static const deleteAndCreateAndroidNotificationChannel = - _FirebaseNotificationsHandlerState.deleteAndCreateAndroidNotificationChannel; + _FirebaseNotificationsHandlerState + .deleteAndCreateAndroidNotificationChannel; /// Creates the provided notification channels. /// @@ -326,7 +337,8 @@ class FirebaseNotificationsHandler extends StatefulWidget { /// when the app was terminated. /// /// {@endtemplate} - static const getInitialMessage = _FirebaseNotificationsHandlerState.getInitialMessage; + static const getInitialMessage = + _FirebaseNotificationsHandlerState.getInitialMessage; /// {@template notificationTapsSubscription} /// @@ -401,10 +413,12 @@ class FirebaseNotificationsHandler extends StatefulWidget { // } @override - State createState() => _FirebaseNotificationsHandlerState(); + State createState() => + _FirebaseNotificationsHandlerState(); } -class _FirebaseNotificationsHandlerState extends State { +class _FirebaseNotificationsHandlerState + extends State { /// Internal [FirebaseMessaging] instance static final _fcm = FirebaseMessaging.instance; @@ -414,24 +428,31 @@ class _FirebaseNotificationsHandlerState extends State? _fcmTokenStreamSubscription; - static final _notificationTapsSubscription = StreamController.broadcast(); - static final _notificationArriveSubscription = StreamController.broadcast(); + static final _notificationTapsSubscription = + StreamController.broadcast(); + static final _notificationArriveSubscription = + StreamController.broadcast(); static StreamSubscription? _onMessageSubscription; static StreamSubscription? _onMessageOpenedAppSubscription; - static Future _createAndroidNotificationChannel(AndroidNotificationChannel channel) async { + static Future _createAndroidNotificationChannel( + AndroidNotificationChannel channel) async { await _flutterLocalNotificationsPlugin - ?.resolvePlatformSpecificImplementation() + ?.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(channel); } - static Future _deleteAndroidNotificationChannel(String channelId) async { + static Future _deleteAndroidNotificationChannel( + String channelId) async { await _flutterLocalNotificationsPlugin - ?.resolvePlatformSpecificImplementation() + ?.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.deleteNotificationChannel(channelId); } - static Future createAndroidNotificationChannel(AndroidNotificationChannel channel) async { + static Future createAndroidNotificationChannel( + AndroidNotificationChannel channel) async { if (!Platform.isAndroid) return; await _initializeLocalNotifications(); @@ -439,7 +460,8 @@ class _FirebaseNotificationsHandlerState extends State deleteAndCreateAndroidNotificationChannel(AndroidNotificationChannel channel) async { + static Future deleteAndCreateAndroidNotificationChannel( + AndroidNotificationChannel channel) async { if (!Platform.isAndroid) return; await _initializeLocalNotifications(); @@ -448,7 +470,8 @@ class _FirebaseNotificationsHandlerState extends State createAndroidNotificationChannels(List channels) async { + static Future createAndroidNotificationChannels( + List channels) async { if (!Platform.isAndroid) return; await _initializeLocalNotifications(); @@ -465,13 +488,15 @@ class _FirebaseNotificationsHandlerState extends State createAndroidNotificationChannelGroup(AndroidNotificationChannelGroup group) async { + static Future createAndroidNotificationChannelGroup( + AndroidNotificationChannelGroup group) async { if (!Platform.isAndroid) return; await _initializeLocalNotifications(); await _flutterLocalNotificationsPlugin - ?.resolvePlatformSpecificImplementation() + ?.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannelGroup(group); } @@ -495,23 +520,27 @@ class _FirebaseNotificationsHandlerState extends State deleteAndroidNotificationChannelGroup(String groupId) async { + static Future deleteAndroidNotificationChannelGroup( + String groupId) async { if (!Platform.isAndroid) return; await _initializeLocalNotifications(); await _flutterLocalNotificationsPlugin - ?.resolvePlatformSpecificImplementation() + ?.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.deleteNotificationChannelGroup(groupId); } - static Future?> getAndroidNotificationChannels() async { + static Future?> + getAndroidNotificationChannels() async { if (!Platform.isAndroid) return null; await _initializeLocalNotifications(); return await _flutterLocalNotificationsPlugin - ?.resolvePlatformSpecificImplementation() + ?.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() ?.getNotificationChannels(); } @@ -527,7 +556,8 @@ class _FirebaseNotificationsHandlerState extends State? payload, TZDateTime? scheduledDateTime, bool shouldForceInitNotifications = false, - UILocalNotificationDateInterpretation? uiLocalNotificationDateInterpretation, + UILocalNotificationDateInterpretation? + uiLocalNotificationDateInterpretation, AndroidScheduleMode? androidScheduleMode, DateTimeComponents? matchDateTimeComponents, }) async { @@ -551,6 +581,11 @@ class _FirebaseNotificationsHandlerState extends State(error: e, stackTrace: s); @@ -644,10 +680,10 @@ class _FirebaseNotificationsHandlerState extends State handleLocalInitialMsg() async { await _initializeLocalNotifications(); - final details = await _flutterLocalNotificationsPlugin?.getNotificationAppLaunchDetails(); + final details = await _flutterLocalNotificationsPlugin + ?.getNotificationAppLaunchDetails(); if (details?.didNotificationLaunchApp ?? false) { if (updateOpenedAppFromNotification) _openedAppFromNotification = true; if (details?.notificationResponse?.notificationResponseType == NotificationResponseType.selectedNotification) { - return RemoteMessage.fromMap(jsonDecode(details!.notificationResponse!.payload!)); + return RemoteMessage.fromMap( + jsonDecode(details!.notificationResponse!.payload!)); } } @@ -893,7 +942,8 @@ class _FirebaseNotificationsHandlerState extends State( - msg: 'Initial message ignored because shouldHandleNotification returned false', + msg: + 'Initial message ignored because shouldHandleNotification returned false', ); return null; @@ -925,8 +975,10 @@ class _FirebaseNotificationsHandlerState extends State DateTime.now().hashCode; + widget.localNotificationsConfiguration.notificationIdGetter ?? + (_) => DateTime.now().hashCode; } void _deactivate() { @@ -993,9 +1046,11 @@ class _FirebaseNotificationsHandlerState extends State=3.4.0 <4.0.0" + dart: ">=3.5.0 <4.0.0" flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0a08fb4..70cf2dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,17 +1,25 @@ name: firebase_notifications_handler -description: Simple notifications handler which provides callbacks like onTap which really make it easy to handle notification taps and a lot more. -version: 1.1.0 +description: Easy-to-use Firebase notifications handler with built-in local notifications support, allowing your app to display notifications in the foreground without extra setup. +version: 2.0.2+1 homepage: https://github.com/rithik-dev/firebase_notifications_handler +repository: https://github.com/rithik-dev/firebase_notifications_handler issue_tracker: https://github.com/rithik-dev/firebase_notifications_handler/issues +# documentation: https://github.com/rithik-dev/firebase_notifications_handler/blob/master/README.md +topics: + - flutter + - firebase + - messaging + - notifications + - push-notifications environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=2.5.0" + # constraints from http, path_provider, firebase_messaging, flutter_local_notifications dependencies + sdk: ^3.3.0 + flutter: '>=3.19.0' scripts: - publish_dry_run: flutter pub publish --dry-run + pre_publish: dart format .; flutter pub publish --dry-run publish_skip_validation: flutter pub publish --skip-validation - publish_force: flutter pub publish --force publish: flutter pub publish dependencies: @@ -21,10 +29,10 @@ dependencies: http: ^1.2.2 path_provider: ^2.1.4 firebase_messaging: ^15.1.2 - flutter_local_notifications: ^17.2.3 + flutter_local_notifications: ^18.0.1 dev_dependencies: - flutter_lints: ^4.0.0 + flutter_lints: ^5.0.0 false_secrets: - /example/lib/firebase_options.dart \ No newline at end of file