I've been working on the Flutter project and now I am trying to schedule notification by using flutter_local_notification. I've integrated that dependency into my project and it works fine.
Now I want to schedule a notification to be alerted on the app like 2 days before, and then 4 hours before, and then 30 minutes before.
For example, my app has an event called A, and then I want to schedule that event A to send a notification for the user like 1 day before, and then 1 hour before, and then 30 minutes before the event A starts. So it will send a notification 3 times.
For my scenario: In order to send a notification we have to pass the notification id. Let's consider event A has id '123'. Do I have to schedule that notification 3 times with the same notification id '123'?
Let's check the code I have done in order to send a notification:
Future<void> showNotification({
#required int id,
#required DateTime dateTime,
String title,
String body,
}) async {
final TimeZone timeZone = TimeZone();
final String tzName = await timeZone.getTimeZoneName();
final tz.Location location = await timeZone.getLocation(tzName);
final scheduledDate = tz.TZDateTime(
location,
dateTime.year,
dateTime.month,
dateTime.day,
dateTime.hour,
dateTime.minute,
dateTime.second,
);
log('------------> Schedule date $scheduledDate');
final AndroidNotificationDetails androidChannelSpecifics =
AndroidNotificationDetails(
'$id',
'NAME $id',
'DESCRIPTION $id',
importance: Importance.max,
priority: Priority.high,
ledOnMs: 1000,
ledOffMs: 500,
);
final IOSNotificationDetails iosNotificationSpecifics =
IOSNotificationDetails();
final notificationDetails = NotificationDetails(
iOS: iosNotificationSpecifics, android: androidChannelSpecifics);
await flutterLocalNotificationsPlugin.zonedSchedule(
id,
title,
body,
scheduledDate,
notificationDetails,
matchDateTimeComponents: DateTimeComponents.time,
uiLocalNotificationDateInterpretation: null,
androidAllowWhileIdle: true,
);
}
For notification initialization like configuring in AndroidManifest.xml, I also have done it already.
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
final BehaviorSubject<ReceivedNotification> didReceivedNotificationSub =
BehaviorSubject<ReceivedNotification>();
InitializationSettings initializationSettings;
LocalNotificationiHelper._() {
init();
}
Future<void> init() async {
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
tz.initializeTimeZones();
if (Platform.isIOS) {
_requestIOSPermission();
}
initialPlatformSpecifics();
}
void initialPlatformSpecifics() {
const AndroidInitializationSettings initializeAndroidSetting =
AndroidInitializationSettings('#mipmap/ic_launcher');
final IOSInitializationSettings initializeIOSSetting =
IOSInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: false,
onDidReceiveLocalNotification:
(int id, String title, String body, String payload) async {
final ReceivedNotification receivedNotification = ReceivedNotification(
id: id, title: title, body: body, payload: payload);
didReceivedNotificationSub.add(receivedNotification);
},
);
initializationSettings = InitializationSettings(
android: initializeAndroidSetting, iOS: initializeIOSSetting);
}
void _requestIOSPermission() {
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
.requestPermissions(
alert: false,
badge: true,
sound: true,
);
}
You might've noticed the class TimeZone(), it is just another class that I created to get the current time zone of the user.
Here is the class for TimeZone()
class TimeZone {
factory TimeZone() => _this ?? TimeZone._();
TimeZone._() {
initializeTimeZones();
}
static TimeZone _this;
Future<String> getTimeZoneName() async =>
FlutterNativeTimezone.getLocalTimezone();
Future<t.Location> getLocation([String timeZoneName]) async {
if (timeZoneName == null || timeZoneName.isEmpty) {
timeZoneName = await getTimeZoneName();
}
return t.getLocation(timeZoneName);
}
}
For example, event A with id 123 will start at 2020-11-19 16:00:00.
So I want to know that should we schedule that notification 3 times in order for it to be sent like 1 day before, 3 hours before, and 30 minutes before the event start??
This depends on how you'd like to set the interval. One way that you can schedule recurring notifications using flutter_local_notifications is by utilizing flutterLocalNotificationsPlugin.periodicallyShow() where you can set intervals like RepeatInterval.everyMinute - usage can be found in the docs.
Related
I'm creating a chat app (kind of WhatsApp-like messaging) using Flutter.
First, the notifications mechanism is working as intended, whenever I send a message from 1 device to another device, the notification would pop up.
I created a local_notification_service.dart to handle the foreground notification & sending a not
import 'dart:math';
import 'package:get/get.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class LocalNotificationService extends GetConnect {
String serverKey ='xxxxxxxxxxxxxxxxxxxx'
static final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
static void initialize() {
const InitializationSettings initializationSettings = InitializationSettings(android: AndroidInitializationSettings("#mipmap/ic_launcher"));
_flutterLocalNotificationsPlugin.initialize(initializationSettings);
}
static void display(RemoteMessage message) async {
try {
print("Display notification");
// int id = DateTime.now().microsecondsSinceEpoch ~/1000000;
Random random = Random();
int id = random.nextInt(1000);
const NotificationDetails notificationDetails = NotificationDetails(
android: AndroidNotificationDetails(
"mychanel",
"my chanel",
importance: Importance.max,
priority: Priority.high,
));
print("my id is ${id.toString()}");
await _flutterLocalNotificationsPlugin.show(
id,
message.notification!.title,
message.notification!.body,
notificationDetails,
);
} on Exception catch (e) {
print('Error>>>$e');
}
}
Future<void> sendNotification({
String? title,
String? message,
String? token,
String? uniqueId,
String? action,
String? channelId,
String? channelName,
String? channelDesc,
}) async {
final data = {
"click_action": "FLUTTER_NOTIFICATION_CLICK",
"action": action,
"uniqueId": uniqueId,
"message": message,
"channelId": channelId ?? 'my channel id',
"channelName": channelName ?? 'my channel Name',
"channelDesc": channelDesc ?? 'my channel description',
};
try {
final response = await post(
'https://fcm.googleapis.com/fcm/send',
{
'notification': {'title': title, 'body': message},
'priority': 'high',
'data': data,
'to': '$token',
'direct_boot_ok': true,
},
headers: {
'Content-Type': 'application/json',
'Authorization': 'key=$serverKey',
},
);
print('response body : ${response.body}');
} catch (e) {}
}
}
Then, I'm trying to validate the users in my flutter application whenever they receive FCM notification, here's the logic that I want to create:
If the user is not logged in, then the device could not receive the notification
If the user is logged in, but the specific user is not eligible to receive the message (in case there are some users with the same FCM token / device registered ) then the device could not receive the notification. I would want to solve this after the point number 1 is succeeded
Here's my main.dart file
void main() async {
await GetStorage.init();
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
GlobalController globalC = Get.put(GlobalController());
AuthController authC = Get.put(AuthController());
ErrorController errorC = Get.put(ErrorController());
ConnectivityResult connectivityResult = ConnectivityResult.none;
final Connectivity connectivity = Connectivity();
connectivityResult = await connectivity.checkConnectivity();
if (connectivityResult == ConnectivityResult.wifi || connectivityResult == ConnectivityResult.mobile) {
// Start FCM
final fcmToken = await FirebaseMessaging.instance.getToken();
globalC.fcmToken.value = fcmToken ?? ''; //set global fcm Token
final FirebaseMessaging fcmInstance = FirebaseMessaging.instance;
NotificationSettings settings = await fcmInstance.requestPermission(
alert: true,
announcement: true,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
/* Handle message when in foreground */
FirebaseMessaging.onMessage.listen((event) {
if (globalC.isAuthenticated.isTrue) {
LocalNotificationService.display(event); //display notification
}
});
/* Handle message when in background */
if (globalC.isAuthenticated.isTrue) {
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
}
fcmInstance.onTokenRefresh.listen((fcmToken) {
// Note: This callback is fired at each app startup and whenever a new
// token is generated.
}).onError((err) {
// Error getting token.
});
// End FCM
}
FirebaseAnalytics analytics = FirebaseAnalytics.instance;
runApp(MyApp());
}
as you can see, I'm trying to filter the non logged in user when in the foreground using the FirebaseMessaging.onMessage.listen with the globalC.isAuthenticated.isTrue validation. And it works (because the default of globalC.isAuthenticated is false whenever user is not logged in)
But for the FirebaseMessaging.onBackgroundMessage function does not seems to work with the validation. I've tried to search for the solution in the documentations, youtube but i couldn't find it till this question is made.
How can I make this kind of validation for background message?
Sorry for this newbie question, any help would be greatly appreciated.
Thank you .
I am making an app in that I want to send a notification to the user with the help of the flutter_local_notification package. I want to send a notification to the user at a certain time every day, for that, I am using the following function:
Future<void> showDailyAtTime() async {
////////// <<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>
var time = Time(16, 02, 00); ////// line 1 <---------- (Hour, Minute, Second)
////////// <<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>
var androidChannelSpecifics = AndroidNotificationDetails(
'CHANNEL_ID 2',
'CHANNEL_NAME 2',
"CHANNEL_DESCRIPTION 2",
importance: Importance.max,
priority: Priority.high,
);
var iosChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics =
NotificationDetails(android: androidChannelSpecifics, iOS: iosChannelSpecifics);
await flutterLocalNotificationsPlugin.showDailyAtTime(
0,
'Test Title at ${time.hour}:${time.minute}.${time.second}',
'Test Body', //null
time,
platformChannelSpecifics,
payload: 'Test Payload',
);
}
I am storing the time using shared preferences like this:
ttime = '${time.hour} : ${time.minute} : ${time.second}';
And then I am setting it like this:
Future<void> _setNotifyTime() async{
final prefs = await SharedPreferences.getInstance();
final savedNotifyTime = await _getStringFromSharedPrefs();
await prefs.setString('notificationTime', ttime);
//print("this $savedNotifyTime");
return savedNotifyTime;
}
Then I am getting from shared preferences with the help of following code:
Future<String> _getStringFromSharedPrefs() async{
final prefs = await SharedPreferences.getInstance();
notifyTime = prefs.getString('notificationTime');
return notifyTime;
}
How can I convert this saved time to the required format (as shown in line 1), the values are in int?
How can I do it? Please help me.
Thanks for your replies in advance
var splited = notifyTime.split(':');
int hour = int.parse(splited[0]);
int minute = int.parse(splited[1]);
int second = int.parse(splited[2]);
You can do pattern splitting like in the answer by Jorge Vieira or you save yourself the parsing logic and just store it as ints.
For setting you can use setInt()
prefs.setInt("H",time.hours);
prefs.setInt("m",time.minutes);
prefs.setInt("s",time.seconds);
For getting it you can use getInt()
int hours = prefs.getInt("H");
int mins = prefs.getInt("m");
int secs = prefs.getInt("s");
Then from your methods just return the Time object
return Time(hours,mins,secs);
As of now for my alarm app, I used the Flutter local notification plugin to schedule notification and play the alarm ringtone by keeping the alarm ringtone as the notification sound in the code.
But because of this, in some Android and iOS devices, the sound is not playing. Is there any way better way to schedule alarms in Flutter?
Here is the code for notification that I did:
class SetAlarm {
static Future<void> setAlarmtoStorage({
int id,
TimeOfDay alarmTime,
}) async {
SharedPreferences pref = await SharedPreferences.getInstance();
pref.setStringList('$id', [alarmTime.hour.toString(), alarmTime.minute.toString()]);
}
static Future<TimeOfDay> getAlarmfromStorage({int id}) async {
SharedPreferences pref = await SharedPreferences.getInstance();
final hourAndMin = pref.getStringList('$id');
final time = TimeOfDay(
hour: int.parse(hourAndMin[0]),
minute: int.parse(hourAndMin[1]),
);
return time;
}
static Future<void> cancelAlarm(int id) async {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin.cancel(id);
}
static Future<void> setAlarm(TimeOfDay selectedTime, int id) async {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
tz.initializeTimeZones();
final String currentTimeZone = await FlutterNativeTimezone.getLocalTimezone();
print(currentTimeZone);
tz.setLocalLocation(tz.getLocation(currentTimeZone));
var notifydate;
final hour = (TimeOfDay.now().hour - selectedTime.hour).abs();
final min = (TimeOfDay.now().minute - selectedTime.minute).abs();
if (tz.TZDateTime.now(tz.local).isAfter(DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
selectedTime.hour,
selectedTime.minute,
))) {
notifydate = tz.TZDateTime.local(DateTime.now().year, DateTime.now().month,
DateTime.now().add(Duration(days: 1)).day, selectedTime.hour, selectedTime.minute);
// tz.TZDateTime.now(tz.local)
// .subtract(Duration(seconds: tz.TZDateTime.now(tz.local).second))
// .add(Duration(days: 1, hours: hour, minutes: min));
print(notifydate.toString());
print('isAfter true(alarm time is after the now date)');
} else {
notifydate = tz.TZDateTime.now(tz.local)
.subtract(Duration(seconds: tz.TZDateTime.now(tz.local).second))
.add(Duration(hours: hour, minutes: min));
print('isAfter:false');
}
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'alarm_notify',
'alarm_notify',
'Channel for Alarm notification',
icon: 'time',
sound: RawResourceAndroidNotificationSound('alarm_ringtone'),
largeIcon: DrawableResourceAndroidBitmap('time'),
playSound: true,
);
var iOSPlatformChannelSpecifics = IOSNotificationDetails(
sound: 'alarm_ringtone.mp3',
presentAlert: true,
presentBadge: true,
presentSound: true,
);
var platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics, iOS: iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.zonedSchedule(
id,
'Alarm',
'Wake Up!!!',
notifydate,
platformChannelSpecifics,
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,
);
}
}
I did all the setup required for flutter local notification. I even kept the sound in the raw folder inside res.
The LocalNotificationsPlugin should be called up every minute with a different payload (variable "custom"). The call is made in a loop. I create a new instance of the plugin class and then initialise it with the settings to use it for each platform. The code works and push notifications are displayed. However, only the most recent message is shown -> the message that last went through the loop. The id is created uniquely from a random number and the time.
Why could not all messages be displayed?
Thanks a lot!
//Loop and create new Push Message
for (var i = 1; i <= final_list.length - 1; i++) {
//Info: Index not 0 because Index 0 value should not be used
final_message = final_list[i];
//Add payload
custom = final_message;
if (i == 1 ){
//First loop -> Selected time plus 1 min
finalmsgtime = selectedTime.add(new Duration(minutes: 1));
} else {
//Second loop and bigger -> finalmsgtime + 2 min //only for test :)
finalmsgtime = finalmsgtime.add(new Duration(minutes: 2));
}
//Date & Time
var now = new DateTime.now();
var notificationTime = new DateTime(
now.year, now.month, now.day, finalmsgtime.hour, finalmsgtime.minute);
//GET ID
var randomizer = new Random();
String id;
var num_id = randomizer.nextInt(10000);
id = '$num_id$now'; //Eindeutige ID
//Set push message
scheduleNotification(
flutterLocalNotificationsPlugin, id, custom, notificationTime);
} //Ende Loop
In this method we create the push message:
Future<void> scheduleNotification(
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin,
String id,
String body,
DateTime scheduledNotificationDateTime) async {
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
id,
'Reminder notifications',
'Remember about it',
icon: 'app_icon',
);
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.schedule(0, 'Quote of the Day', body, //Titel von Push-Nachricht
scheduledNotificationDateTime, platformChannelSpecifics);
}
I found the solution. The Problem was that flutterLocalNotificationsPlugin.schedule(...) was called with the static value '0' for ID and not with the variable. After I changed this, the ID for each notification was unique and the notifications have been displayed correctly.
Future<void> scheduleNotification(
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin,
String id,
String body,
DateTime scheduledNotificationDateTime) async {
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
id,
'Reminder notifications',
'Remember about it',
icon: 'app_icon',
);
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
var myID = int.parse(id);
assert(myID is int);
myID = myID - 1000;
await flutterLocalNotificationsPlugin.schedule(myID, 'Quote of the Day', body, //Titel von Push-Nachricht
scheduledNotificationDateTime, platformChannelSpecifics);
}
I would like to ask if there's a http get in flutter local notifications? My aim here is when the flutter notification will show, a http get request will be triggered
This my http request for my api:
final String url = 'http://192.168.43.45:8000/api';//url in my request
List data;
Future<String> getData() async {
var response =await http.get(
Uri.encodeFull(url),
headers:{"Accept":"application/json"}
);//get data and decode it to json
}
This code will initialize the notifications:
initializeNotifications() async {
var initializationSettingsAndroid =
AndroidInitializationSettings('#mipmap/launcher_icon');//icon will display when notification appears
var initializationSettingsIOS = IOSInitializationSettings();
var initializationSettings = InitializationSettings(
initializationSettingsAndroid, initializationSettingsIOS);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
}
Future onSelectNotification(String payload) async {
if (payload != null) {
debugPrint('notification payload: ' + payload);
}
await Navigator.push(
context,
new MaterialPageRoute(builder: (context) => HomePage()),
);
}
Code for the notification when click it will redirect to homePage:
Future<void> scheduleNotification(Medicine medicine) async {
var hour = int.parse(medicine.startTime[0] + medicine.startTime[1]);
var ogValue = hour;
var minute = int.parse(medicine.startTime[2] + medicine.startTime[3]);
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'repeatDailyAtTime channel id',
'repeatDailyAtTime channel name',
'repeatDailyAtTime description',
importance: Importance.Max,
ledColor: Color(0xFF3EB16F),
ledOffMs: 1000,
ledOnMs: 1000,
enableLights: true,
);
//notification details
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
//this code will show the whats the notification must appear
await flutterLocalNotificationsPlugin.showDailyAtTime(
int.parse(medicine.notificationIDs[i]),
'Mediminder: ${medicine.medicineName}',
medicine.medicineType.toString() !=
MedicineType.None.toString()
? 'It is time to take your Medicine, according to schedule'
: 'It is time to take your medicine, according to schedule',
Time(hour, minute,),
platformChannelSpecifics);
hour = ogValue;
}
//await flutterLocalNotificationsPlugin.cancelAll();//cancel the flutter notifications
}
}
it seems that you are using flutter_local_notifications, looking at their documentation i don't think it's possible to you to handle the onReceive notification, this is done internally by the package.
But maybe you could implement your own receiver class extending BroadCastReceiver and listen for the same action that the packages send, from there you'll be able to send HTTP requests.
Take a look at this question, maybe it helps.