I've been trying to get Flutter local notifications to work since a week but can't get it to work.
Basically the issue is whenever i create a daily notification, it works only for the first time and then it doesn't show notifications every next day.
Suppose if i set daily scheduled notification at 12:20 PM, it will show notification at 12:20 PM for first time, then the next day it won't show. And when i see the list of pending notifications i can see the notification still present.
here's all my notification code
class NotificationService {
// Singleton pattern
static final NotificationService _notificationService =
NotificationService._internal();
factory NotificationService() {
return _notificationService;
}
NotificationService._internal();
static const channelId = "1";
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
static const AndroidNotificationDetails _androidNotificationDetails =
AndroidNotificationDetails(
channelId,
"thecodexhub",
channelDescription:
"This channel is responsible for all the local notifications",
playSound: true,
priority: Priority.high,
importance: Importance.high,
);
static const IOSNotificationDetails _iOSNotificationDetails =
IOSNotificationDetails();
final NotificationDetails notificationDetails = const NotificationDetails(
android: _androidNotificationDetails,
iOS: _iOSNotificationDetails,
);
Future<void> init() async {
const AndroidInitializationSettings androidInitializationSettings =
AndroidInitializationSettings('#mipmap/ic_launcher');
const IOSInitializationSettings iOSInitializationSettings =
IOSInitializationSettings(
defaultPresentAlert: false,
defaultPresentBadge: false,
defaultPresentSound: false,
);
const InitializationSettings initializationSettings =
InitializationSettings(
android: androidInitializationSettings,
iOS: iOSInitializationSettings,
);
// *** Initialize timezone here ***
tz.initializeTimeZones();
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onSelectNotification: onSelectNotification,
);
}
Future<void> requestIOSPermissions() async {
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
}
Future<void> showNotification(
int id, String title, String body, String payload) async {
await flutterLocalNotificationsPlugin.show(
id,
title,
body,
notificationDetails,
payload: payload,
);
}
Future<void> scheduleNotification(int id, String title, String body,
DateTime eventDate, TimeOfDay eventTime, String payload,
[DateTimeComponents? dateTimeComponents]) async {
final scheduledTime = eventDate.add(Duration(
hours: eventTime.hour,
minutes: eventTime.minute,
));
await flutterLocalNotificationsPlugin.zonedSchedule(
id,
title,
body,
tz.TZDateTime.from(scheduledTime, tz.local),
notificationDetails,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
androidAllowWhileIdle: true,
payload: payload,
matchDateTimeComponents: dateTimeComponents,
);
}
Future<void> cancelNotification(int id) async {
await flutterLocalNotificationsPlugin.cancel(id);
}
Future<void> cancelAllNotifications() async {
await flutterLocalNotificationsPlugin.cancelAll();
}
Future getNotifications() async {
final List<PendingNotificationRequest> pendingNotificationRequests =
await FlutterLocalNotificationsPlugin().pendingNotificationRequests();
return pendingNotificationRequests;
}
}
Future<void> onSelectNotification(String? payload) async {
// await navigatorKey.currentState
// ?.push(MaterialPageRoute(builder: (_) => DetailsPage(payload: payload)));
}
and here's how i'm calling it.
await notificationService.scheduleNotification(
1,
_textEditingController.text,
"Reminder for your scheduled event at ${eventTime!.format(context)}",
eventDate!,
eventTime!,
jsonEncode({
"title": _textEditingController.text,
"eventDate": DateFormat("EEEE, d MMM y").format(eventDate!),
"eventTime": eventTime!.format(context),
}),
getDateTimeComponents(),
);
}
here is getDateTimeComponents if it matters
DateTimeComponents? getDateTimeComponents() {
if (segmentedControlGroupValue == 1) {
return DateTimeComponents.time;
} else if (segmentedControlGroupValue == 2) {
return DateTimeComponents.dayOfWeekAndTime;
}
}
it's been week since i'm trying to fix this issue.
Thank you for reading.
Here it says
Use zonedSchedule instead by passing a date in the future with the
same time and pass DateTimeComponents.matchTime as the value of the
matchDateTimeComponents parameter.
You seem to use DateTimeComponents.time correctly but I guess your date is not in the future. Can you try adding like a thousand years to your date and see? Maybe because after the first firing, the date is now in the past and it will not fire on the next day because of it.
Related
I am using flutter local notification to display schedule notification in my app. But unfortunately it is not working when app is in terminated state.
Here is my code:
class Notifications {
static final FlutterLocalNotificationsPlugin _notifications =
FlutterLocalNotificationsPlugin();
static Future<NotificationDetails> _notificationDetails() async {
return const NotificationDetails(
android: AndroidNotificationDetails(
'weekly notification channel id',
'weekly notification channel name',
channelDescription: 'weekly notification description',
playSound: true,
sound: RawResourceAndroidNotificationSound('azan1'),
importance: Importance.high,
),
iOS: IOSNotificationDetails(sound: 'azan1.mp3', presentSound: true));
}
static void init() async {
tz.initializeTimeZones();
const AndroidInitializationSettings android =
AndroidInitializationSettings('#mipmap/ic_launcher');
const IOSInitializationSettings iOS = IOSInitializationSettings();
InitializationSettings settings =
const InitializationSettings(android: android, iOS: iOS);
await _notifications.initialize(settings);
final String locationName = await FlutterNativeTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation(locationName));
}
static void showScheduledNotification(
int id, {
required DateTime scheduledDate,
String? title,
String? body,
String? payload,
}) async {
await _notifications.zonedSchedule(
id,
'Azan Time',
'$body Prayer Time',
_scheduleDaily(
Time(scheduledDate.hour, scheduledDate.minute, scheduledDate.second)),
await _notificationDetails(),
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.time,
payload: payload,
);
}
static tz.TZDateTime _scheduleDaily(Time time) {
tz.TZDateTime now = tz.TZDateTime.now(tz.local);
tz.TZDateTime schdeuledDate = tz.TZDateTime(tz.local, now.year, now.month,
now.day, time.hour, time.minute, time.second);
return schdeuledDate.isBefore(now)
? schdeuledDate.add(const Duration(days:1))
: schdeuledDate;
}
static Future<void> cancelNotification(int id) async {
await _notifications.cancel(id);
}
static Future<void> cancelAllNotifications() async {
await _notifications.cancelAll();
}
}
I have also added all properties in Android.xml file.
But still it is not working if anybody know the solution of this problem kindly answer this question.
In your Application.java if this part not work you can escape it to run the app .
private void createNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channelHigh = new NotificationChannel(
CHANNEL_HIGH,
"Notificación Importante",
NotificationManager.IMPORTANCE_HIGH
);
channelHigh.setDescription("Canal Alto");
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channelHigh);
}
}
Check the Manifest File and do not forget to add
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="default"/>
For More Check This Link
Local notifications might be a bit tricky. Look at the flutter_local_notifications README file:
Some Android OEMs have their own customised Android OS that can prevent applications from running in the background. Consequently, scheduled notifications may not work when the application is in the background on certain devices (e.g. by Xiaomi, Huawei). If you experience problems like this then this would be the reason why. As it's a restriction imposed by the OS, this is not something that can be resolved by the plugin. Some devices may have setting that lets users control which applications run in the background. The steps for these can vary but it is still up to the users of your application to do given it's a setting on the phone itself.
It has been reported that Samsung's implementation of Android has imposed a maximum of 500 alarms that can be scheduled via the Alarm Manager API and exceptions can occur when going over the limit.
Source:
https://pub.dev/packages/flutter_local_notifications#scheduled-android-notifications
If you could provide your main function, it would have been helpful. I'll give you a general example of how to create any scheduled notification.
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:rxdart/rxdart.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationApi {
static final _notification = FlutterLocalNotificationsPlugin();
static final onNotifications = BehaviorSubject<String?>();
static Future _notificationDetails() async {
return const NotificationDetails(
android: AndroidNotificationDetails(
'channel id',
'channel name',
channelDescription: 'Update users on new deal',
importance: Importance.max,
enableLights: true,
),
iOS: IOSNotificationDetails(),
);
}
static Future init({bool initScheduled = true}) async {
const android = AndroidInitializationSettings('#drawable/ic_notcion');
const iOS = IOSInitializationSettings();
const settings = InitializationSettings(android: android, iOS: iOS);
/// when app is closed
final details = await _notification.getNotificationAppLaunchDetails();
if (details != null && details.didNotificationLaunchApp) {
onNotifications.add(details.payload);
}
await _notification.initialize(
settings,
onSelectNotification: (payload) async {
onNotifications.add(payload);
},
);
if(initScheduled){
tz.initializeTimeZones();
final locationName = await FlutterNativeTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation(locationName));
}
}
static tz.TZDateTime _scheduledDaily(Time time) {
final now = tz.TZDateTime.now(tz.local);
final scheduledDate = tz.TZDateTime(tz.local, now.year, now.month, now.day,
time.hour);
return scheduledDate.isBefore(now)
? scheduledDate.add(const Duration(days: 1))
: scheduledDate;
}
static tz.TZDateTime _scheduleWeekly(Time time, {required List<int> days}) {
tz.TZDateTime scheduledDate = _scheduledDaily(time);
while (!days.contains(scheduledDate.weekday)) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
return scheduledDate;
}
static Future showWeeklyScheduledNotification({
int id = 8,
String? title,
String? body,
String? payload,
required DateTime scheduledDate,
}) async =>
_notification.zonedSchedule(
id,
title,
body,
_scheduleWeekly(const Time(17), days: [
DateTime.tuesday,
DateTime.friday,
DateTime.saturday,
DateTime.sunday,
]),
// tz.TZDateTime.from(scheduledDate, tz.local),
await _notificationDetails(),
payload: payload,
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime,
);
static void cancelAll() => _notification.cancelAll();
}
On the main function, init the NotificationApi as follows:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
//__________________________________Notification and Time Zone
tz.initializeTimeZones();
await NotificationApi.init();
await NotificationApi.init(initScheduled: true);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// This widget is the root of your application.
///_____________________Init state initialising notifications
#override
void initState() {
loadAllNotifications;
super.initState();
}
loadAllNotifications() {
NotificationApi.showWeeklyScheduledNotification(
title: '🏆 New Deals Available 🏆',
body: '✨ Don\'t miss your opportunity to win BIG 💰💰',
scheduledDate: DateTime.now().add(const Duration(seconds: 12)));
NotificationApi.init(initScheduled: true);
listenNotifications();
}
//___________________________Listen to Notifications
void listenNotifications() =>
NotificationApi.onNotifications.stream.listen(onClickedNotification);
//__________________________On Notification Clicked
void onClickedNotification(String? payload) => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => AppWrapper(updates: updates)));
//________________________________________Widget Build
#override
Widget build(BuildContext context) => MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Alpha Deals',
home: MyHomePage()
);
}
I need to make like this notification
My Output code
my Pubspec.yaml file. I am using latest version of flutter_local_notifications
flutter_local_notifications: ^9.2.0
This is my Notification Controller code
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:image/image.dart' as image;
import 'package:path_provider/path_provider.dart';
class NotificationService {
static final NotificationService _notificationService =
NotificationService._internal();
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
factory NotificationService() {
return _notificationService;
}
NotificationService._internal();
Future<void> init(
Function(int, String?, String?, String?)?
onDidReceiveLocalNotification) async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('#mipmap/launcher_icon');
final IOSInitializationSettings initializationSettingsIOS =
IOSInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false,
onDidReceiveLocalNotification:
(int id, String? title, String? body, String? payload) async {},
);
final InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onSelectNotification: (String? payload) async {
if (payload != null) {
print('notification payload: $payload');
}
},
);
}
Future selectNotification(String? payload) async {
print('selectNotification: $payload');
}
Future<String> _downloadAndSaveFile(String url, String fileName) async {
final Directory? directory = await getExternalStorageDirectory();
final String filePath = '${directory!.path}/$fileName.png';
final http.Response response = await http.get(Uri.parse(url));
final File file = File(filePath);
await file.writeAsBytes(response.bodyBytes);
return filePath;
}
Future showNotify({
required String body,
required String image,
}) async {
final String largeIconPath = await _downloadAndSaveFile(
'https://via.placeholder.com/48x48',
'largeIcon',
);
final String bigPicturePath = await _downloadAndSaveFile(
image,
'bigPicture',
);
await flutterLocalNotificationsPlugin.show(
123,
'New Buisness Sutra Updated',
body,
NotificationDetails(
android: AndroidNotificationDetails(
'big text channel id',
'big text channel name',
ongoing: true,
priority: Priority.max,
largeIcon: FilePathAndroidBitmap(largeIconPath),
channelDescription: 'big text channel description',
styleInformation: BigPictureStyleInformation(
FilePathAndroidBitmap(bigPicturePath),
hideExpandedLargeIcon: false,
contentTitle: 'overridden <b>big</b> content title',
htmlFormatContentTitle: true,
summaryText: 'summary <i>text</i>',
htmlFormatSummaryText: true,
),
),
),
payload: 'data',
);
}
}
I initialized the firebase and notification on my main.dart file
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (!kIsWeb) {
await Firebase.initializeApp();
firebaseCloudMessagingListeners();
await NotificationService().init();
}
runApp(const MyApp());
}
firebaseCloudMessagingListeners() {
FirebaseMessaging.onBackgroundMessage(_messageHandler);
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print(message);
_messageHandler(message);
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print("Message opened");
});
}
Future<void> _messageHandler(RemoteMessage message) async {
Map<String, dynamic> data = message.data;
print(data);
await NotificationService().showNotify(
body: data["body"],
image: data["image"],
);
}
The firebase_messaging plugin works fine. The problem is with the big picture on the notification from flutter_local_notifications.
I download the image by using path_provider and http. also this image succesfully downloaded on my app External storage directory. But not shown on the notification
/storage/emulated/0/Android/data/in.myapp.dhrona/files/largeIcon.png
/storage/emulated/0/Android/data/in.myapp.dhrona/files/bigIcon.png
Any solutions for this issue
To show network image,
final http.Response response = await http.get(Uri.parse(URL));
BigPictureStyleInformation bigPictureStyleInformation =
BigPictureStyleInformation(
ByteArrayAndroidBitmap.fromBase64String(base64Encode(image)),
largeIcon: ByteArrayAndroidBitmap.fromBase64String(base64Encode(image)),
);
flutterLocalNotificationsPlugin.show(
Random().nextInt(1000),
title,
description,
NotificationDetails(
android: AndroidNotificationDetails(channel.id, channel.name,
channelDescription: channel.description,
importance: Importance.high,
styleInformation: bigPictureStyleInformation),
),
);
To show local image,
final String filePath = '${directory.path}/$fileName';
final BigPictureStyleInformation bigPictureStyleInformation =
BigPictureStyleInformation(FilePathAndroidBitmap(filePath),
largeIcon: FilePathAndroidBitmap(filePath));
flutterLocalNotificationsPlugin.show(
Random().nextInt(1000),
title,
description,
NotificationDetails(
android: AndroidNotificationDetails(channel.id, channel.name,
channelDescription: channel.description,
importance: Importance.high,
styleInformation: bigPictureStyleInformation),
),
);
With AwesomeNotifications I don't need to download. I just give the url like below:
Future<void> showNotificationChatMessage(
RemoteMessage message, MyMessage myMessage) async {
final chatId = message.data['chatId'];
NotificationContent? notificationContent;
if (myMessage.type == MyMessageType.text ||
myMessage.replyType == MyMessageReplyType.text) {
notificationContent = NotificationContent(
id: 10,
channelKey: 'basic_channel',
title: message.data['title'].toString(),
body: message.data['body'].toString(),
payload: {'chatId': chatId.toString()});
} else if (myMessage.type == MyMessageType.image ||
myMessage.replyType == MyMessageReplyType.image) {
notificationContent = NotificationContent(
id: 11,
channelKey: 'big_picture',
title: message.data['title'].toString(),
body: 'Image',
bigPicture: myMessage.message,
showWhen: true,
displayOnBackground: true,
payload: {'chatId': chatId.toString()},
notificationLayout: NotificationLayout.BigPicture);
}
if (notificationContent != null) {
AwesomeNotifications().createNotification(content: notificationContent);
}
}
Although the answer by #Vignesh works, here are some improvements to that answer:
response is not used in the example
there is no need to set the "largeIcon" property with the same image data as the big image because it will show the same image two times in the notification when the notification is expanded.
the final answer should look like this:
final http.Response response = await http.get(Uri.parse(imageUrl));
bigPictureStyleInformation = BigPictureStyleInformation(
ByteArrayAndroidBitmap.fromBase64String(base64Encode(response.bodyBytes)));
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title ?? 'APP_NAME',
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channelDescription: channel.description,
styleInformation: bigPictureStyleInformation,
// other properties...
),
)
);
I'm using flutterlocalnotifications package and FCM to send notifications to my users. Everything working perfectly. I can show notifications to my users when my app is in the background, foreground, or closed. I can reach payload value in foreground and background, but when the app is killed I can't reach payload anymore. I test in catlog but there isn't any print in the onSelectNotification.
How can reach this payload?
This is my notification handler class
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
AndroidNotificationChannel channel = AndroidNotificationChannel(
'high_importance_channel', // id
'High Importance Notifications', // title
'This channel is used for important notifications.', // description
importance: Importance.high,
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// await Firebase.initializeApp();
//Bildirim geldiği anda datayı elde ettiğimiz alan
final dynamic data = message.data;
print('Data in background : ${data.toString()}');
NotificationHandler.showNotification(data);
}
class NotificationHandler {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
static final NotificationHandler _singleton = NotificationHandler._internal();
factory NotificationHandler() {
return _singleton;
}
NotificationHandler._internal();
BuildContext myContext;
initializeFCMNotifications(BuildContext context) async {
await _firebaseMessaging.setForegroundNotificationPresentationOptions(
alert: true, badge: true, sound: true);
var initializationSettingsAndroid =
AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = IOSInitializationSettings(
onDidReceiveLocalNotification: onDidReceiveLocalNotification);
var initializationSettings = InitializationSettings(
android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
await _firebaseMessaging.subscribeToTopic("all");
await FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage message) {
if (message != null) {
print('initialmessage tetiklendi : ' + message.data.toString());
}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
var notification = message.notification;
var android = message.notification?.android;
print('ONMESSAGE ÇALIŞTI');
print('onmessage tetiklendi data : ' +
message.data['message'] +
' title ' +
message.data['title']);
showNotification(message.data);
});
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('Onmessageopened : ' + message.data.toString());
});
}
static void showNotification(Map<String, dynamic> data) async {
const androidPlatformChannelSpecifics = AndroidNotificationDetails(
'1234', 'Yeni Mesaj', 'your channel description',
importance: Importance.max, priority: Priority.high, ticker: 'ticker');
const IOSPlatformChannelSpecifics = IOSNotificationDetails();
const platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: IOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0, data['title'], data['message'], platformChannelSpecifics,
payload: 'The value when notification tapped');
}
Future onSelectNotification(String payload) async {
if (payload != null) {
debugPrint('notification payload : $payload');
}
}
Future onDidReceiveLocalNotification(
int id, String title, String body, String payload) {}
}
and these are the versions of the packages I use
firebase_messaging: ^9.1.1
flutter_local_notifications: ^5.0.0+1
I am trying to set a scheduled alarm notification from the user selected date and time which i used showDatePicker for code below
DateTime _selectedDateAndTime;
Future _selectDayAndTimeL(BuildContext context) async {
DateTime _selectedDay = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2021),
lastDate: DateTime(2030),
builder: (BuildContext context, Widget child) => child);
TimeOfDay _selectedTime = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (_selectedDay != null && _selectedTime != null) {
//a little check
}
setState(() {
_selectedDateAndTime = DateTime(
_selectedDay.year,
_selectedDay.month,
_selectedDay.day,
_selectedTime.hour,
_selectedTime.minute,
);
// _selectedDate = _selectedDay;
});
// print('...');
}
which after the date and time has been selected the value is formatted like in the picture bellow
Now i want to be able to set the Scheduled Notification using the value from the selection but not sure how to do it... i have installed Flutter_Local_Notification and have imported it to my main.dart, have set the permission in the manifest file and have also tried to initial the plugin like down bellow
FlutterLocalNotificationsPlugin fltrNotification;
String _selectedParam;
int val;
#override
void initState() {
super.initState();
var androidInitilize = new AndroidInitializationSettings('app_icon');
var iOSinitilize = new IOSInitializationSettings();
var initilizationsSettings =
new InitializationSettings(androidInitilize, iOSinitilize);
fltrNotification = new FlutterLocalNotificationsPlugin();
fltrNotification.initialize(initilizationsSettings,
onSelectNotification: notificationSelected);
}
and i have also added the app_icon.png to my drawable folder
i have tried to follow some tutorial on how to do it but most of them only show how to set the netification using seconds but for my own project i want to set the schedule for a particular day, hour and minute
please how can i achive that?
You can use this my helper class
class NotificationPlugin {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
var initializationSettings;
NotificationPlugin._() {
init();
}
init() async {
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
if (Platform.isIOS) {
_requestIOSPermission();
}
initializePlatformSpecifics();
}
initializePlatformSpecifics() {
var initializationSettingsAndroid = AndroidInitializationSettings(
'mipmap/ic_launcher'); // <- default icon name is #mipmap/ic_launcher
var initializationSettingsIOS =
IOSInitializationSettings(onDidReceiveLocalNotification: (int a, String b, String c, d) {});
var initializationSettings =
InitializationSettings(initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String s) {});
initializationSettings =
InitializationSettings(initializationSettingsAndroid, initializationSettingsIOS);
}
_requestIOSPermission() {
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
.requestPermissions(alert: false, badge: true, sound: true);
}
setOnNotificationClick(Function onNotificationClick) async {
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onSelectNotification: (payload) async {
onNotificationClick(payload);
},
);
}
Future<void> showNotification(
{#required int id, #required String title, #required String body}) async {
var androidChannelSpecifics = AndroidNotificationDetails(
'CHANNEL_ID',
'CHANNEL_NAME',
'CHANNEL_DESCRIPTION',
importance: Importance.High,
priority: Priority.High,
);
var iosChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidChannelSpecifics,
iosChannelSpecifics,
);
await flutterLocalNotificationsPlugin.show(id, title, body, platformChannelSpecifics,
payload: id.toString());
}
Future<void> showScheduledNotification(
{#required int id,
#required String title,
#required String body,
#required String date}) async {
var scheduledNotificationDateTime = DateTime.parse(date);
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'your other channel id', 'your other channel name', 'your other channel description');
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
NotificationDetails platformChannelSpecifics =
NotificationDetails(androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.schedule(
id, '$title', ' $body', scheduledNotificationDateTime, platformChannelSpecifics,
androidAllowWhileIdle: true);
}
Future<void> removeNotifications() async {
await flutterLocalNotificationsPlugin.cancelAll();
}
}
NotificationPlugin notificationPlugin = NotificationPlugin._();
and then you can call
await notificationPlugin.showScheduledNotification(
id: 123,
title:"fancy title",
body: "data",
date: yourDate,
);
/////////////////////////////////
if you will not use the helper class
this is simply what you need
var scheduledNotificationDateTime = DateTime.parse(date); // replace whith your date
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'your other channel id', 'your other channel name', 'your other channel description');
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
NotificationDetails platformChannelSpecifics =
NotificationDetails(androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.schedule(
id, '$title', ' $body', scheduledNotificationDateTime, platformChannelSpecifics,
androidAllowWhileIdle: true);
This is first time I am testing FCM with Flutter. I checked some of the SO questions and documents from GitHub.
I am able to send the notifications and they are getting delivered when the app is not running.
If app is running or in the background then messages are not visible.
I have added the code in main.dart file but not sure this is the correct way or not.
Edit:
This is for onResume:
{notification: {}, data: {badge: 1, collapse_key: com.HT, google.original_priority: high, google.sent_time: 1623238, google.delivered_priority: high, sound: default, google.ttl: 2419200, from: 71374876, body: Body, title: Title, click_action: FLUTTER_NOTIFICATION_CLICK, google.message_id: 0:50a56}}
In the Below code, i am trying to use local notifications with FCM.
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
new FlutterLocalNotificationsPlugin();
#override
void initState() {
var initializationSettingsAndroid =
new AndroidInitializationSettings('#mipmap/ic_launcher');
var initializationSettingsIOS = new IOSInitializationSettings();
var initializationSettings = new InitializationSettings(
android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
showNotification(
message['notification']['title'], message['notification']['body']);
print("onMessage: $message");
},
onLaunch: (Map<String, dynamic> message) async {
print("onLaunch: $message");
// Navigator.pushNamed(context, '/notify');
ExtendedNavigator.of(context).push(
Routes.bookingQRScan,
);
},
onResume: (Map<String, dynamic> message) async {
print("onResume: $message");
},
);
}
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: AnimatedSplashScreen(), //SplashScreen()
builder: ExtendedNavigator.builder<a.Router>(router: a.Router()),
);
}
Future onSelectNotification(String payload) async {
showDialog(
context: context,
builder: (_) {
return new AlertDialog(
title: Text("PayLoad"),
content: Text("Payload : $payload"),
);
},
);
}
void showNotification(String title, String body) async {
await _demoNotification(title, body);
}
Future<void> _demoNotification(String title, String body) async {
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'channel_ID', 'channel name', 'channel description',
importance: Importance.max,
playSound: false, //true,
//sound: 'sound',
showProgress: true,
priority: Priority.high,
ticker: 'test ticker');
//var iOSChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin
.show(0, title, body, platformChannelSpecifics, payload: 'test');
}
}
Error
This is when my app is running on foreground. E/FlutterFcmService(14434): Fatal: failed to find callback
W/FirebaseMessaging(14434): Missing Default Notification Channel metadata in AndroidManifest. Default value will be used.
W/ConnectionTracker(14434): Exception thrown while unbinding
W/ConnectionTracker(14434): java.lang.IllegalArgumentException: Service not registered: lu#fb04880
Notification is visible in notification center. Now i am clicking on it and app get terminated.
and new instance of app is running and below is the return code. I/flutter (14434): onResume: {notification: {}, data: {badge: 1, collapse_key: com.HT, google.original_priority: high, google.sent_time: 1607733798, google.delivered_priority: high, sound: default, google.ttl: 2419200, from: 774876, body: Body, title: Title, click_action: FLUTTER_NOTIFICATION_CLICK, google.message_id: 0:1607573733816296%850a56}}
E/FlutterFcmService(14434): Fatal: failed to find callback
W/ConnectionTracker(14434): Exception thrown while unbinding
Edit 2:
I did more digging and come up with the below code.
final FirebaseMessaging _fcm = FirebaseMessaging();
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
var initializationSettingsAndroid;
var initializationSettingsIOS;
var initializationSettings;
void _showNotification() async {
//await _buildNotification();
}
Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) {
if (message.containsKey('data')) {
// Handle data message
final dynamic data = message['data'];
}
if (message.containsKey('notification')) {
// Handle notification message
final dynamic notification = message['notification'];
}
// Or do other work.
}
Future<void> _createNotificationChannel(
String id, String name, String description) async {
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
var androidNotificationChannel = AndroidNotificationChannel(
id,
name,
description,
importance: Importance.max,
playSound: true,
// sound: RawResourceAndroidNotificationSound('not_kiddin'),
enableVibration: true,
);
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(androidNotificationChannel);
}
Future<void> _buildNotification(String title, String body) async {
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'my_channel', 'Channel Name', 'Channel Description.',
importance: Importance.max,
priority: Priority.high,
// playSound: true,
enableVibration: true,
// sound: RawResourceAndroidNotificationSound('not_kiddin'),
ticker: 'noorderlicht');
//var iOSChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin
.show(0, title, body, platformChannelSpecifics, payload: 'payload');
}
#override
void initState() {
super.initState();
initializationSettingsAndroid =
AndroidInitializationSettings('#mipmap/ic_launcher');
initializationSettingsIOS = IOSInitializationSettings(
onDidReceiveLocalNotification: onDidReceiveLocalNotification);
initializationSettings =
InitializationSettings(android: initializationSettingsAndroid);
// initializationSettingsAndroid, initializationSettingsIOS);
_fcm.requestNotificationPermissions();
_fcm.configure(
onMessage: (Map<String, dynamic> message) async {
print(message);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
//_showNotification();
Map.from(message).map((key, value) {
print(key);
print(value);
print(value['title']);
_buildNotification(value['title'], value['body']);
});
},
onLaunch: (Map<String, dynamic> message) async {
print("onLaunch: $message");
},
onResume: (Map<String, dynamic> message) async {
print("onResume: $message");
print(message['data']['title']);
//AlertDialog(title: message['data']['title']);
ExtendedNavigator.of(context).push(
Routes.bookingQRScan,
);
//_showNotification();
},
);
}
Future onDidReceiveLocalNotification(
int id, String title, String body, String payload) async {
// display a dialog with the notification details, tap ok to go to another page
showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text(title),
content: Text(body),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
child: Text('Ok'),
onPressed: () {},
)
],
),
);
}
Future onSelectNotification(String payload) async {
if (payload != null) {
debugPrint('Notification payload: $payload');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: AnimatedSplashScreen(), //SplashScreen()
builder: ExtendedNavigator.builder<a.Router>(router: a.Router()),
);
}
With above code i can see notifications in notification bar but onresume section i want to redirection that is not working. Not sure why.
Also I want to show alert box in onmeesage and onresume events.
Your payload must be correct, notification and data object inside the payload must contain the title and body key. you will get title and body null when your app is closed in notification key in that situation you should have title and body in the side data key.
{notification: {title: title, body: test}, data: {notification_type: Welcome, body: body, badge: 1, sound: , title: farhana mam, click_action: FLUTTER_NOTIFICATION_CLICK, message: H R U, category_id: 2, product_id: 1, img_url: }}
and don't put title and body null
void showNotification(Map<String, dynamic> msg) async {
//{notification: {title: title, body: test}, data: {notification_type: Welcome, body: body, badge: 1, sound: , title: farhana mam, click_action: FLUTTER_NOTIFICATION_CLICK, message: H R U, category_id: 2, product_id: 1, img_url: }}
print(msg);
print(msg['data']['title']);
var title = msg['data']['title'];
var msge = msg['data']['body'];
var android = new AndroidNotificationDetails(
'channel id', 'channel NAME', 'CHANNEL DESCRIPTION',
priority: Priority.High, importance: Importance.Max);
var iOS = new IOSNotificationDetails();
var platform = new NotificationDetails(android, iOS);
await flutterLocalNotificationsPlugin.show(0, title, msge, platform,
payload: msge);
}
for redirection
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
var android = new AndroidInitializationSettings('#mipmap/ic_launcher');
var iOS = new IOSInitializationSettings();
var initSetttings = new InitializationSettings(android, iOS);
flutterLocalNotificationsPlugin.initialize(initSetttings, onSelectNotification: onSelectNotification);
firebaseCloudMessaging_Listeners();
Future onSelectNotification(String payload) async {
if (payload != null) {
debugPrint('notification payload:------ ${payload}');
await Navigator.push(
context,
new MaterialPageRoute(builder: (context) => NotificationListing()),
).then((value) {});
}
}
in 'onSelectNotification' you can pass your condition in string parama and you can redirect
(optional, but recommended) If want to be notified in your app (via onResume and onLaunch, see below) when the user clicks on a notification in the system tray include the following intent-filter within the tag of your android/app/src/main/AndroidManifest.xml:
Looking at your (last edited) code above, I think first you have to make sure, whether localnotifications is used or the default fcm one. Since your myBackgroundMessageHandler does not do anything, I assume the latter one. Try replacing the title temporarily with a fixed string (e.g. "this is a local one") to make sure.
Secondly, myBackgroundMessageHandler will only be called for data messages. If you use the payload you wrote in the beginning, you should be fine. Anyway make sure to not put title, body, style-information etc directly in the payload. If you need it, put it in the data node.
This is the code I am using:
calling the notificationService init() method in main.dart
notification-service.dart
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:app/models/data-notification.dart';
import 'dart:async';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'dart:io';
FlutterLocalNotificationsPlugin notificationsPlugin =
FlutterLocalNotificationsPlugin();
//Function to handle Notification data in background.
Future<dynamic> backgroundMessageHandler(Map<String, dynamic> message) {
print("FCM backgroundMessageHandler $message");
showNotification(DataNotification.fromPushMessage(message['data']));
return Future<void>.value();
}
//Function to handle Notification Click.
Future<void> onSelectNotification(String payload) {
print("FCM onSelectNotification");
return Future<void>.value();
}
//Function to Parse and Show Notification when app is in foreground
Future<dynamic> onMessage(Map<String, dynamic> message) {
print("FCM onMessage $message");
showNotification(DataNotification.fromPushMessage(message['data']));
return Future<void>.value();
}
//Function to Handle notification click if app is in background
Future<dynamic> onResume(Map<String, dynamic> message) {
print("FCM onResume $message");
return Future<void>.value();
}
//Function to Handle notification click if app is not in foreground neither in background
Future<dynamic> onLaunch(Map<String, dynamic> message) {
print("FCM onLaunch $message");
return Future<void>.value();
}
void showNotification(DataNotification notification) async {
final AndroidNotificationDetails androidPlatformChannelSpecifics =
await getAndroidNotificationDetails(notification);
final NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await notificationsPlugin.show(
0,
notification.title,
notification.body,
platformChannelSpecifics,
);
}
Future<AndroidNotificationDetails> getAndroidNotificationDetails(
DataNotification notification) async {
switch (notification.notificationType) {
case NotificationType.NEW_INVITATION:
case NotificationType.NEW_MEMBERSHIP:
case NotificationType.NEW_ADMIN_ROLE:
case NotificationType.MEMBERSHIP_BLOCKED:
case NotificationType.MEMBERSHIP_REMOVED:
case NotificationType.NEW_MEMBERSHIP_REQUEST:
return AndroidNotificationDetails(
'organization',
'Organization management',
'Notifications regarding your organizations and memberships.',
importance: Importance.max,
priority: Priority.high,
showWhen: false,
category: "Organization",
icon: 'my_app_icon_simple',
largeIcon: DrawableResourceAndroidBitmap('my_app_icon'),
styleInformation: await getBigPictureStyle(notification),
sound: RawResourceAndroidNotificationSound('slow_spring_board'));
case NotificationType.NONE:
default:
return AndroidNotificationDetails('general', 'General notifications',
'General notifications that are not sorted to any specific topics.',
importance: Importance.max,
priority: Priority.high,
showWhen: false,
category: "General",
icon: 'my_app_icon_simple',
largeIcon: DrawableResourceAndroidBitmap('my_app_icon'),
styleInformation: await getBigPictureStyle(notification),
sound: RawResourceAndroidNotificationSound('slow_spring_board'));
}
}
Future<BigPictureStyleInformation> getBigPictureStyle(
DataNotification notification) async {
if (notification.imageUrl != null) {
print("downloading");
final String bigPicturePath =
await _downloadAndSaveFile(notification.imageUrl, 'bigPicture');
return BigPictureStyleInformation(FilePathAndroidBitmap(bigPicturePath),
hideExpandedLargeIcon: true,
contentTitle: notification.title,
htmlFormatContentTitle: false,
summaryText: notification.body,
htmlFormatSummaryText: false);
} else {
print("NOT downloading");
return null;
}
}
Future<String> _downloadAndSaveFile(String url, String fileName) async {
final Directory directory = await getApplicationDocumentsDirectory();
final String filePath = '${directory.path}/$fileName';
final http.Response response = await http.get(url);
final File file = File(filePath);
await file.writeAsBytes(response.bodyBytes);
return filePath;
}
class NotificationService {
FirebaseMessaging _fcm = FirebaseMessaging();
void init() async {
final AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('app_icon');
final IOSInitializationSettings initializationSettingsIOS =
IOSInitializationSettings();
final InitializationSettings initializationSettings =
InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
await notificationsPlugin.initialize(initializationSettings,
onSelectNotification: (value) => onSelectNotification(value));
_fcm.configure(
onMessage: onMessage,
onBackgroundMessage: backgroundMessageHandler,
onLaunch: onLaunch,
onResume: onResume,
);
}
}
data-notification.dart
import 'package:enum_to_string/enum_to_string.dart';
class DataNotification {
final String id;
final String title;
final String body;
final NotificationType notificationType;
final String imageUrl;
final dynamic data;
final DateTime readAt;
final DateTime createdAt;
final DateTime updatedAt;
DataNotification({
this.id,
this.title,
this.body,
this.notificationType,
this.imageUrl,
this.data,
this.readAt,
this.createdAt,
this.updatedAt,
});
factory DataNotification.fromPushMessage(dynamic data) {
return DataNotification(
id: data['id'],
title: data['title'],
body: data['body'],
notificationType: EnumToString.fromString(
NotificationType.values, data['notification_type']),
imageUrl: data['image_url'] ?? null,
data: data,
readAt: null,
createdAt: null,
updatedAt: null,
);
}
}
enum NotificationType {
NONE,
NEW_INVITATION,
NEW_MEMBERSHIP,
NEW_ADMIN_ROLE,
MEMBERSHIP_BLOCKED,
MEMBERSHIP_REMOVED,
NEW_MEMBERSHIP_REQUEST
}
You can ignore the DataNotification model part, and parse the notification yourself, I just used it for some additional interactions with in the backend.
This works well for me, however, if you want to show an alert for "onSelectNotification" or alike, you need to find a way to get the context there. Not (yet) sure, how to do that.
EDIT:
You can call it like this in main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
NotificationService().init();
runApp(
MyApp()
);
}
Be aware that there is currently an issue with backgroundmessaging and hot-reloading: https://github.com/FirebaseExtended/flutterfire/issues/4316