Related
I am developing an app using Flutter, and I am implementing push notification using FCM with the firebase_messaging: ^10.0.4 Flutter plugin:
I am using Firebase to send notification on mobile app while the mobile app is in terminated state. To get the notification in terminated state, I am using FirebaseMessaging.instance.getInitialMessage() to handle the on click of notification. When the user clicks on the notification, they will be routed to a specific screen (which shows the message passed).
The issue is I am getting the notification in mobile app in terminated state, but when I click on the notification, I am not routed to the specific screen which I passed in from Firebase and FirebaseMessaging.instance.getInitialMessage() value is getting null in message.
Please let me know If anyone have idea about this.
main.dart
checkFirebase() async {
await Firebase.initializeApp();
// Set the background messaging handler early on, as a named top-level function
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
if (!kIsWeb) {
channel = const AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
description: 'This channel is used for important notifications.',
// description
importance: Importance.max,
);
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
/// Create an Android Notification Channel.
///
/// We use this channel in the `AndroidManifest.xml` file to override the
/// default FCM channel to enable heads up notifications.
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
/// Update the iOS foreground notification presentation options to allow
/// heads up notifications.showFrontNotification
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
}
}
class _ProcessAppState extends State<ProcessApp> {
Future<void> _initializeFuture;
Future<void> _initializeServices() async {
await Firebase.initializeApp();
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
var dir = await getApplicationDocumentsDirectory();
Hive.init(dir.path);
// pass all uncaught errors to crashlytics
Function originalOnError = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails errorDetails) async {
await FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
originalOnError(errorDetails);
};
// Set the background messaging handler early on, as a named top-level function
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
if (!kIsWeb) {
channel = const AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
description: 'This channel is used for important notifications.',
// description
importance: Importance.max,
);
/*
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
/// Create an Android Notification Channel.
///
/// We use this channel in the `AndroidManifest.xml` file to override the
/// default FCM channel to enable heads up notifications.
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
*/
/// Update the iOS foreground notification presentation options to allow
/// heads up notifications.
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
}
}
#override
void initState() {
super.initState();
_initializeFuture = _initializeServices();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: FutureBuilder(
future: _initializeFuture,
builder: (context, snapshot) {
if (snapshot.hasError) {
reportError(
error: snapshot.error, stackTrace: snapshot.stackTrace);
return Center(
child: Text(context.translateText(key: "general_error")),
);
}
if (snapshot.connectionState == ConnectionState.done) {
return MyApp();
}
return progressBar();
},
),
),
);
}
}
#override
Widget build(BuildContext context) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: gradientTopColor,
statusBarBrightness: Brightness.dark,
),
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: primaryColor,
backgroundColor: whiteColor,
fontFamily: 'Roboto'),
supportedLocales: [
Locale('en'),
Locale('ta'),
Locale('ml'),
Locale('kn'),
Locale('te'),
],
localizationsDelegates: [
// for our own localizations
AppLocalizations.delegate,
// localizations for all material widgets provided
GlobalMaterialLocalizations.delegate,
// localizations for all cupertino widgets provided
DefaultCupertinoLocalizations.delegate,
// for rtl, ltr text directions
GlobalWidgetsLocalizations.delegate,
],
locale: _locale,
localeResolutionCallback: (deviceLocale, supportedLocales) {
try {
return Locale(defaultLang);
} catch (e) {
print(e);
return Locale("en");
}
},
// navigation analytics reporting
// navigatorObservers: <NavigatorObserver>[observer],
home: NotificationMessageHandler(child: LauncherScreen()),
builder: EasyLoading.init(),
),
);
}
message_handler.dart
class _NotificationMessageHandlerState extends State<NotificationMessageHandler>
with AfterLayoutMixin<NotificationMessageHandler> {
#override
void initState() {
super.initState();
// _checkForUpdate();
var initializationSettingsAndroid =
AndroidInitializationSettings("#mipmap/ic_launcher");
var initializationSettings =
InitializationSettings(android: initializationSettingsAndroid);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: (payload) async {
log("payload : $payload", name: "onSelectNotification");
handleNotificationClick(context, jsonDecode(payload));
});
FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage initialMessage) async {
log("message : $initialMessage", name: "getInitialMessage");
handleNotificationClick(context, jsonDecode(initialMessage.toString()));
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print(message);
showFrontNotification(message);
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
handleNotificationClick(context, message.data);
});
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
When your app is terminated and you want to navigate to another screen you need context from your MaterialApp's navigatorKey and also a keyword where you actually want to go, the keyword we will "click_action" key in the FCM request body.
I would recommend you handle your Firebase Messaging code in a separate file.
fcm_service.dart
This file contains route navigator in foreground, background and after app terminated, also handles notification with an image.
import 'dart:convert';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;
import '../main.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'custom_notification_channel_id',
'Notification',
description: 'notifications from Your App Name.',
importance: Importance.high,
);
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
}
void setupFcm() {
var initializationSettingsAndroid = const AndroidInitializationSettings('#mipmap/ic_launcher');
var initializationSettingsIOs = const IOSInitializationSettings();
var initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOs,
);
//when the app is in foreground state and you click on notification.
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String payload) {
if (payload != null) {
Map<String, dynamic> data = json.decode(payload);
goToNextScreen(data);
}
});
//When the app is terminated, i.e., app is neither in foreground or background.
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage message) {
//Its compulsory to check if RemoteMessage instance is null or not.
if (message != null) {
goToNextScreen(message.data);
}
});
//When the app is in the background, but not terminated.
FirebaseMessaging.onMessageOpenedApp.listen((event) {
goToNextScreen(event.data);
},
cancelOnError: false,
onDone: () {},
);
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
RemoteNotification notification = message.notification;
AndroidNotification android = message.notification?.android;
if (notification != null && android != null) {
if (android.imageUrl != null && android.imageUrl.trim().isNotEmpty) {
final String largeIcon = await _base64encodedImage(
android.imageUrl,
);
final BigPictureStyleInformation bigPictureStyleInformation =
BigPictureStyleInformation(
ByteArrayAndroidBitmap.fromBase64String(largeIcon),
largeIcon: ByteArrayAndroidBitmap.fromBase64String(largeIcon),
contentTitle: notification.title,
htmlFormatContentTitle: true,
summaryText: notification.body,
htmlFormatSummaryText: true,
hideExpandedLargeIcon: true,
);
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channelDescription: channel.description,
icon: 'custom_notification_icon',
color: primaryColor,
importance: Importance.max,
priority: Priority.high,
largeIcon: ByteArrayAndroidBitmap.fromBase64String(largeIcon),
styleInformation: bigPictureStyleInformation,
),
),
payload: json.encode(message.data),
);
}
else {
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channelDescription: channel.description,
icon: 'custom_notification_icon',
color: primaryColor,
importance: Importance.max,
priority: Priority.high,
),
),
payload: json.encode(message.data),
);
}
}
});
}
Future<void> deleteFcmToken() async {
return await FirebaseMessaging.instance.deleteToken();
}
Future<String> getFcmToken() async {
String token = await FirebaseMessaging.instance.getToken();
return Future.value(token);
}
void goToNextScreen(Map<String, dynamic> data) {
if (data['click_action'] != null) {
switch (data['click_action']) {
case "first_screen":
navigatorKey.currentState.pushNamed(FirstScreen.routeName,);
break;
case "second_screen":
navigatorKey.currentState.pushNamed(SecondScreen.routeName,);
break;
case "sample_screen":
navigatorKey.currentState.pushNamed(SampleScreen.routeName,);
}
return;
}
//If the payload is empty or no click_action key found then go to Notification Screen if your app has one.
navigatorKey.currentState.pushNamed(NotificationPage.routeName,);
}
Future<String> _base64encodedImage(String url) async {
final http.Response response = await http.get(Uri.parse(url));
final String base64Data = base64Encode(response.bodyBytes);
return base64Data;
}
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
runApp(const MyApp());
}
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
class MyApp extends StatefulWidget {
const MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
setupFcm();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
onGenerateRoute: //Define your named routes.
);
}
}
Also, you need to define the default notification channel id, and optionally default notification icon, default notification color
AndroidManifest.xml
<application>
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="custom_notification_channel_id" />
<!-- Set custom default icon. This is used when no icon is set for incoming notification messages. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="#drawable/custom_notification_icon" />
<!-- Set color used with incoming notification messages. This is used when no color is set for the incoming notification message. -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="#color/notification_icon_color" />
</application>
Also you can check sample FCM HTTP Request for handling "data" key JsonObject and "click_action" in it.
URL: https://fcm.googleapis.com/fcm/send
Request Method: POST
Header: {
"Authorization": "key={value1}",
"Sender": "id={value2}",
}
Request Body: {
"registration_ids": [
"exwlH32_S0il4ky4ZRXCrg:APA91bHp4kL-IJmtHRGFcQlhUauEY1ZiqZFfWsDkWqsB-yHDUzRVx63e8ehSirUTbSg6NqMqAAfcW16tk4dgs-NtTcCVShipGt9JWIJK_r8b4ldqFYGhzZcNF0VTiVKWzWkRQQIncCoE"
],
"notification": {
"title": "Wear Mask",
"body": "Maintain social distance",
"image": "https://repository-images.githubusercontent.com/31792824/fb7e5700-6ccc-11e9-83fe-f602e1e1a9f1",
"imageUrl": "https://repository-images.githubusercontent.com/31792824/fb7e5700-6ccc-11e9-83fe-f602e1e1a9f1",
"sound": "default"
},
"data": {
"click_action": "sample_screen",
"custom_key": "custom_value",
"image": "https://repository-images.githubusercontent.com/31792824/fb7e5700-6ccc-11e9-83fe-f602e1e1a9f1",
"imageUrl": "https://repository-images.githubusercontent.com/31792824/fb7e5700-6ccc-11e9-83fe-f602e1e1a9f1"
}
}
Note: registration_ids key only takes 1000 values in the list.
In "data" JsonObject you can define your custom key-value pair, which will come in handy. e.g., you want to open a specific screen let's say an event_screen.dart and you need to fetch the event details from the server by event id. So you can prepare your "data" object accordingly
"data": {
"click_action": "event_screen",
"event_id": "23"
}
I know I am late to the party but may this help someone in future. I was facing the same issue and I used the above answer by Smeet Bhatt but I was still facing the same issue. What I noticed that I was clicking too fast may be await Firebase.initializeApp(); was not finished yet. When I waited for a while, RemoteMessage was never null on FirebaseMessaging.instance.getInitialMessage().
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
print('Handling a background message ${message.messageId}');
}
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
I can display background notifications with these snippet codes. But is there any way to not display some specific notification in the background?
For example I don't want to display notifications with data = {title = "call"}
from the sender device u have to send data only notification
await http.post(
Uri.parse('https://fcm.googleapis.com/fcm/send'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'key=$serverKey',
},
body: jsonEncode(
<String, dynamic>{
'data': <String, dynamic>{
'id': '2',
'status': 'done'
},
'to': userToken,
"collapse_key": uid,
},
),
);
add conditions in your _firebaseMessagingBackgroundHandler like
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
if(message.data["title"]=="Call")
{
// show nothing
}
else
{
// show notifications
}
}
I'm developing a Flutter app (using Firebase Firestore and FCM) that aims to send notifications to all devices where the user has signed in. When the user signs out from a device, the token will be removed from Firebase. I am looking for a solution to remove the token when user uninstall the app, and in a way, able to store/update the multiple device tokens for a single user without storing the token based on the android id.
What I am using for sending notification:
final FirebaseMessaging firebaseMessaging = FirebaseMessaging();
Future<Map<String, dynamic>> sendAndRetrieveMessage(context) async {
await firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(sound: true, badge: true, alert: true, provisional: false),
);
await http.post(
'https://fcm.googleapis.com/fcm/send',
headers: <String, String>{
'Content-Type': 'application/json',
'Authorization': 'key=$server_token',
},
body: jsonEncode(
<String, dynamic>{
'notification': <String, dynamic>{
'body': '${widget.content}',
'title': '${widget.title}',
},
'priority': 'high',
'data': <String, dynamic>{
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'status': 'done',
'body': '${widget.content}',
'title': '${widget.title}',
},
'registration_ids': user_tokens,
},
),
).then((http.Response response){
final int statusCode = response.statusCode;
print("RESPONSE BODY: " + response.body);
print("STATUS CODE: " + statusCode.toString());
if (statusCode < 200 || statusCode > 400 || response.body == null) {
throw new Exception("Error while fetching data");
}
});
final Completer<Map<String, dynamic>> completer =
Completer<Map<String, dynamic>>();
firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
completer.complete(message);
},
onLaunch: (Map<String, dynamic> message) async {
completer.complete(message);
},
onResume: (Map<String, dynamic> message) async {
completer.complete(message);
},
);
return completer.future;
}
I have tried these methods for my notification module:
Update signed-in token in Firebase, but updating only one string also means only sending to the latest signed-in device of the user ignoring the other devices
Subscribe user to user_id topic, but topics are limited to 5 per sending notification. My app needs to send to a list of specific users (undetermined by a common topic, but a set of multiple conditions) and I want to avoid using loops as the notification function needs to work simultaneously for all receiving users
Read HTTP response message to detect failed token, the response returns "NotRegistered" but the response does not return the specific failed token
I would like to know if this is doable in Flutter and if there are solutions/simpler methods for this problem. Thank you
Currently I have topics to send push notifications. In particular
all: this is a topic in which I send notifications to everyone
paid: this is a topic in which I send notification only to a small group of users
Today I was upgrading Flutter to the newest firebase package and I encountered this:
<String, dynamic>{
'notification': <String, dynamic>{
'body': 'this is a body',
'title': 'this is a title'
},
'priority': 'high',
'data': <String, dynamic>{
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'id': '1',
'status': 'done'
},
'to': await firebaseMessaging.getToken(),
},
But wait, isn't 'to' meant to send notifications only to a specific device? Why does
'to': await firebaseMessaging.getToken(),
this send a message to all devices? I am confused because the doc says that 'to' is for specific targets too. Thanks!
The to property in an FCM message determines where it is sent to. The value of the to property can be the device token of a single device, it can be the ID of a device group, or it can be a topic.
It sounds like in your actual code you pass a topic in to, while in the sample in your question it passes a device token.
you can use this Firestore api by google to send push notifications
Future<bool> callOnFcmApiSendPushNotifications(List <String> userToken) async
{
final postUrl = 'https://fcm.googleapis.com/fcm/send';
final data = {
"registration_ids" : userToken,
"collapse_key" : "type_a",
"notification" : {
"title": 'NewTextTitle',
"body" : 'NewTextBody',
}
};
final headers = {
'content-type': 'application/json',
'Authorization': constant.firebaseTokenAPIFCM // 'key=YOUR_SERVER_KEY'
};
final response = await http.post(postUrl,
body: json.encode(data),
encoding: Encoding.getByName('utf-8'),
headers: headers);
if (response.statusCode == 200) {
// on success do sth
print('test ok push CFM');
return true;
} else {
print(' CFM error');
// on failure do sth
return false;
}
}
I have built an application with flutter that works like a reminder.
How can I display notifications to the user even though the app is closed?
For reminders i would recomend Flutter Local Notifications Plugin. It has a powerful scheduling api. From the documentation of local notification:
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 - Ability to handle when a user has tapped on a notification when the app is the foreground, background or terminated
And for push notification, you can use Firebase Cloud Messaging
or one signal plugin or you can implement natively through platform-channels
Edit: You can also fire notifications according to specific conditions even if the app is terminated. This can be achevied by running dart code in the background. Quoting from the official faq:
Can I run Dart code in the background of an Flutter app? Yes, you can
run Dart code in a background process on both iOS and Android. For
more information, see the Medium article Executing Dart in the
Background with Flutter Plugins and Geofencing.
I have found a solution to this problem. We just have to register the Local Notification Plugin in the Application class.
First Create a class FlutterLocalNotificationPluginRegistrant, I have created this in Kotlin.
class FlutterLocalNotificationPluginRegistrant {
companion object {
fun registerWith(registry: PluginRegistry) {
if (alreadyRegisteredWith(registry)) {
Log.d("Local Plugin", "Already Registered");
return
}
FlutterLocalNotificationsPlugin.registerWith(registry.registrarFor("com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin"))
Log.d("Local Plugin", "Registered");
}
private fun alreadyRegisteredWith(registry: PluginRegistry): Boolean {
val key = FlutterLocalNotificationPluginRegistrant::class.java.canonicalName
if (registry.hasPlugin(key)) {
return true
}
registry.registrarFor(key)
return false
}
}}
Now create a Application class extending FlutterApplication and implement PluginRegistry.PluginRegistrantCallback.
class Application : FlutterApplication(), PluginRegistry.PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
}
override fun registerWith(registry: PluginRegistry?) {
if (registry != null) {
FlutterLocalNotificationPluginRegistrant.registerWith(registry)
}
}}
and register the Application class in the AndroidManifest.xml
<application
android:name="com.packagename.Application"/>
All done. Now write a function to show notification and call it from the background handler method of Firebase messaging.
Future _showNotificationWithDefaultSound(String title, String message) async {
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'channel_id', 'channel_name', 'channel_description',
importance: Importance.Max, priority: Priority.High);
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0,
'$title',
'$message',
platformChannelSpecifics,
payload: 'Default_Sound',
);
}
and call it like this.
Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) async {
if (message['data'] != null) {
final data = message['data'];
final title = data['title'];
final body = data['message'];
await _showNotificationWithDefaultSound(title, message);
}
return Future<void>.value();
}
I have also faced this issue, So these are my learnings
In my Case : i am able to get notification in App-Resume or App-background state, but in App-Close state, I am not receiving notifification.
In this case our notification body was :
{notification: {body: null, title: null}, data: {body: hello, title: world}}
To Receive Notification in App-Closed state we changed notification to
{notification: {body: abc, title: abc}, data: {url: string, body: string, title: string}}
You can use scheduled notifications in flutter.
var scheduledNotificationDateTime =
new DateTime.now().add(new Duration(seconds: 5));
var androidPlatformChannelSpecifics =
new AndroidNotificationDetails('your other channel id',
'your other channel name', 'your other channel description');
var iOSPlatformChannelSpecifics =
new IOSNotificationDetails();
NotificationDetails platformChannelSpecifics = new
NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.schedule(
0,
'scheduled title',
'scheduled body',
scheduledNotificationDateTime,
platformChannelSpecifics);
For those who are using the latest version around 2.2 just call the firebaseMessageInstance
FirebaseMessaging.instance.getInitialMessage().then((message) =>
message.messageId.isNotEmpty
? print('we can now navigate to specific screen')
: print('there is no new notification so default screen will be shown when application start from terminated state'));
Don't forget to call the
Navigator.push(
context, MaterialPageRoute(builder: (context) => YourScreenName()));
when message.messageId.isNotEmpty
upvote if you like this approach thanks have a good coding day
If you do not need to connect to the Internet, you can use this packages flutter local notification && flutter native timezone
after add the package to pubspace.ymal
add this code to android/app/src/main/AndroidManifest.xml
<activity
android:showWhenLocked="true"
android:turnScreenOn="true">
also in ios folder open if you used swift Runner/AppDelegate.swift in function didFinishLaunchingWithOptions add
if #available(iOS 10.0, *) {UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate}
if you used Object-C Runner/AppDelegate.m in function didFinishLaunchingWithOptions add
if (#available(iOS 10.0, *)) {[UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
}
after that you should add app-icon to drawable folder
then import the packages import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz; in file dart create and add
class NotifyHelper {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
String selectedNotificationPayload = '';
final BehaviorSubject<String> selectNotificationSubject =
BehaviorSubject<String>();
initializeNotification() async {
tz.initializeTimeZones();
_configureSelectNotificationSubject();
await _configureLocalTimeZone();
// await requestIOSPermissions(flutterLocalNotificationsPlugin);
final IOSInitializationSettings initializationSettingsIOS =
IOSInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
onDidReceiveLocalNotification: onDidReceiveLocalNotification,
);
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('appicon');
final InitializationSettings initializationSettings =
InitializationSettings(
iOS: initializationSettingsIOS,
android: initializationSettingsAndroid,
);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onSelectNotification: (String? payload) async {
if (payload != null) {
debugPrint('notification payload: ' + payload);
}
selectNotificationSubject.add(payload!);
},
);
}
displayNotification({required String title, required String body}) async {
print('doing test');
var androidPlatformChannelSpecifics = const AndroidNotificationDetails(
'your channel id', 'your channel name', 'your channel description',
importance: Importance.max, priority: Priority.high);
var iOSPlatformChannelSpecifics = const IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0,
title,
body,
platformChannelSpecifics,
payload: 'Default_Sound',
);
}
// this is the scheduled notification
// Task is a model class have a data item like title, desc, start time and end time
scheduledNotification(int hour, int minutes, Task task) async {
await flutterLocalNotificationsPlugin.zonedSchedule(
task.id!,
task.title,
task.note,
//tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
_nextInstanceOfTenAM(hour, minutes),
const NotificationDetails(
android: AndroidNotificationDetails(
'your channel id', 'your channel name', 'your channel description'),
),
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.time,
payload: '${task.title}|${task.note}|${task.startTime}|',
);
}
tz.TZDateTime _nextInstanceOfTenAM(int hour, int minutes) {
final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
tz.TZDateTime scheduledDate =
tz.TZDateTime(tz.local, now.year, now.month, now.day, hour, minutes);
if (scheduledDate.isBefore(now)) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
return scheduledDate;
}
void requestIOSPermissions() {
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
}
Future<void> _configureLocalTimeZone() async {
tz.initializeTimeZones();
final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation(timeZoneName));
}
/* Future selectNotification(String? payload) async {
if (payload != null) {
//selectedNotificationPayload = "The best";
selectNotificationSubject.add(payload);
print('notification payload: $payload');
} else {
print("Notification Done");
}
Get.to(() => SecondScreen(selectedNotificationPayload));
} */
//Older IOS
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: const Text('Title'),
content: const Text('Body'),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
child: const Text('Ok'),
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Container(color: Colors.white),
),
);
},
)
],
),
);
*/
Get.dialog( Text(body!));
}
//I used Get package Get here to go screen notification
void _configureSelectNotificationSubject() {
selectNotificationSubject.stream.listen((String payload) async {
debugPrint('My payload is ' + payload);
await Get.to(() => NotificationScreen(payload));
});
}
}
use object from this class and call the scheduledNotificationmethod