Daily recurring notifications in flutter - flutter

I am trying to use the plugin, flutter_local_notifications to send recurring notifications to the user, every day but the resource available for the plugin consists of only code and is incomprehensible, I have done the setup for the plugin as follows:
added the dependency of flutter_local_notifications
added the permission code for iOS in the AppDelegate.swift
I did this much by referring to numerous resources like medium and a few other sites, so can someone please write a method which sends the user (android & iOS) a recurring notification everyday? Thank you!

After writing that comment I did a bit of research and this is working for me so far:
Future<void> showAlertNotification() async {
var time = Time(8, 0, 0);
var androidChannel = AndroidNotificationDetails(
'channelID', 'channelName', 'channelDescription',
importance: Importance.defaultImportance,
priority: Priority.defaultPriority,
playSound: true);
var iosChannel = IOSNotificationDetails();
var platformChannel =
NotificationDetails(android: androidChannel, iOS: iosChannel);
await flutterLocalNotificationsPlugin.showDailyAtTime(2, 'notification title',
'message here', time, platformChannel,
payload: 'new payload'));
}

You should not use showDailyAtTime as of now it has quite a number of problems like syncing up with local time zones. So instead you should use the zoned schedule but you should first initialize it with your local time zone it can be done with another package called flutter_native_timezone. This is the workflow I used:
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest.dart' as tz;
Future<void> showNotif() async {
tz.initializeTimeZones();
String dtz = await FlutterNativeTimezone.getLocalTimezone();
if (dtz == "Asia/Calcutta") {
dtz = "Asia/Kolkata";
}
final localTimeZone = tz.getLocation(dtz);
tz.setLocalLocation(localTimeZone);
await _flutterLocalNotificationsPlugin.zonedSchedule(..., matchDateTimeComponents: DateTimeComponents.time);
}
// The ... above is the usual parameters that I didn't write.

Related

Notification not showing when app is closed - Flutter

I have built a todo list app where users can schedule notifications based on certain dates and hours, the notifications show up without problems when the app is in the foreground but when the app is in the background or the app is closed, they do not show up, and I cannot understand why, any reason why, Thank you.
This is my code
late FlutterLocalNotificationsPlugin _localNotificationsPlugin;
List<Task> _list = [];
late MoorController moorController;
#override
void initState() {
super.initState();
moorController = Get.put(MoorController());
createNotification();
//showNotification();
}
void createNotification(){
_localNotificationsPlugin = FlutterLocalNotificationsPlugin();
var initializationSettingsAndroid = const AndroidInitializationSettings("#mipmap/ic_launcher");
var initializationSettingsIOs = const IOSInitializationSettings();
var initSettings = InitializationSettings(android: initializationSettingsAndroid,iOS: initializationSettingsIOs);
_localNotificationsPlugin.initialize(initSettings);
}
void showNotification() async {
var android = const AndroidNotificationDetails(
Utils.CHANNEL_ID,
Utils.CHANNEL_NAME,
channelDescription: Utils.DESCRIPTION,
priority: Priority.high,
importance: Importance.max);
var iOS = const IOSNotificationDetails();
var platform = NotificationDetails(android: android,iOS: iOS);
for (var element in moorController.tasks) {
if(element.date == getCurrentDate() && element.time == getCurrentTime()){
await _localNotificationsPlugin.schedule(
0,element.date,element.time,DateTime.now(),platform, payload : 'Welcome to todo app',androidAllowWhileIdle: true);
//0, element.date,element.time,platform, payload: 'Simple Payload'
}
}
}
Receiver Manifest File
Permissions
I believe you need an instance of the TZDateTime from the timezone package in order to schedule a notification.
This is a snippet from the documentation:
Starting in version 2.0 of the plugin, scheduling notifications now requires developers to specify a date and time relative to a specific time zone. This is to solve issues with daylight savings that existed in the schedule method that is now deprecated. A new zonedSchedule method is provided that expects an instance TZDateTime class provided by the timezone package. As the flutter_local_notifications plugin already depends on the timezone package, it's not necessary for developers to add the timezone package as a direct dependency. In other words, the timezone package will be a transitive dependency after you add the flutter_local_notifications plugin as a dependency in your application.
So now in your code, import the package
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
Initialize the timezone database:
tz.initializeTimeZones();
Once the time zone database has been initialised, developers may optionally want to set a default local location/time zone
tz.setLocalLocation(tz.getLocation(timeZoneName));
Now you should be able to call it like this:
await _localNotificationsPlugin.zonedSchedule(
0,
'title',
'body',
tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
platform,
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime);
More info can be found in the docs here
Let us know if this helps!
You should check if your payload contains the click_action with FLUTTER_NOTIFICATION_CLICK :
{
"notification": {
"body": "this is a body",
"title": "this is a title",
},
"data": {
"click_action": "FLUTTER_NOTIFICATION_CLICK",
"sound": "default",
"status": "done",
"screen": "screenA",
},
"to": "<FCM TOKEN>"
}'
Did you add these permissions in your AndroidManifest.xml
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
Inside the application section:
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"><intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action></intent-filter></receiver>
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />

Notifications every day for a particular date range

So, I have an app where users are reminded to take medicines every day at a particular time for a certain interval of dates. For example, the user can choose to get a notification from September 16,2020 to September 18,2020 at some time of the day
My approach : I schedule a notification using the flutter_local_notifications package with showDailyAtTime() function. However, the problem I face is that, suppose I don't open the app again, there is no way to cancel the scheduled notification and thus, the notification pops up even after the specified date range. I would like the notifications to be offline, so Firebase doesn't seem to be an option.
You can solve the problem with FlutterLocalNotificationsPlugin.
The approach would be to call the method rescheduleNotifications every time you start the app. In the method all notifications are removed and the next notifications are set. In calculateNotificationTimes for example you calculate all notifications for the next 30 days. For example, all notifications on September 16, 2020 to September 18, 2020 each day at a time of your choice.
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
Future<void> rescheduleNotifications() async {
final localNotificationsPlugin = FlutterLocalNotificationsPlugin();
const initializationSettings = InitializationSettings(AndroidInitializationSettings('app_icon'), IOSInitializationSettings());
const androidChannelSpecifics = AndroidNotificationDetails('your channel id', 'your channel name', 'your channel description');
const iOSNotificationDetails = IOSNotificationDetails();
const notificationsDetails = NotificationDetails(androidChannelSpecifics, iOSNotificationDetails);
await localNotificationsPlugin.initialize(initializationSettings);
await localNotificationsPlugin.cancelAll();
// Calculate the next notifications.
final notificationTimes = calculateNotificationTimes();
var _currentNotificationId = 0;
for (final time in notificationTimes) {
localNotificationsPlugin.schedule(
_currentNotificationId++,
"It's time to take your medicine.",
'Take the red pill',
time,
notificationsDetails,
androidAllowWhileIdle: true,
);
}
}
On iOS there is a limit that you can only have 64 notifications enabled. The disadvantage of this method on iOS is that if the user does not open the app after 64 notifications, no notification will be displayed. Which is fine, I think, because it seems that the user does not use the app anymore.
Did not test the code.

How to set Local Notification for multiple day in weekdays in Flutter?

I am working on a flutter app, In which using flutter_local_notifications for the local notification. Weekly notification is working fine for selecting the only day on weekdays. Now I want to pick multiple days on weekdays like (Monday, Thursday, and Saturday). I couldn't find any solution to implement. Sharing my sample code.
showNotificationWeekly() {
var time = Time(10, 0, 0);
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'id',
'name',
'description',
);
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(
0,
'show weekly title',
'Weekly notification',
Day.Monday,
time,
platformChannelSpecifics,
);
}
Please let me know, If any solution for this so that I can set one notification for multiple days
I think you will need to schedule multiple Notifications, one for each day :/
Thanks, It require unique notification ID for each notification.

Use flutter_local_notifications plugin in Isolate

I have some business logic that requires to create a number of scheduled notifications, from 1 to n with n being defined prior running the code that loops until it reaches n. I have to loop from 1 to n as I use each count as index to access some elements of a list.
Psuedo code for it is something like:
for(int i=1; i<=50; i++){
// below is a function that calls into an initialized instace of FlutterLocalNotificationsPlugin()
// that I use to access the notification scheduler helper
createNotification(i);
}
Running it seems fine both on my emulator and actual device. However, I found out that running that part of the code on older or weaker-specced device would cause the UI to momentarily be unresponsive or laggy. From my research, I am thinking that the logic and code that I am using is too taxing for the Main Isolate and should be put on a separate one for parallel execution that will free up my Main Isolate to maintain my UI's performance.
Below is the code for the high-level class called by the Flutter compute helper for Isolates.
test(int _num) async {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
var android = AndroidInitializationSettings('#mipmap/ic_launcher');
var iOS = IOSInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
);
var initializationSettings = InitializationSettings(android, iOS);
var androidDetails = AndroidNotificationDetails(
'channelId',
'channelName',
'channelDescription',
importance: Importance.Max,
priority: Priority.High,
);
var iOSDetails = IOSNotificationDetails();
NotificationDetails platformChannelSpecifics = NotificationDetails(androidDetails, iOSDetails);
await flutterLocalNotificationsPlugin.initialize(initializationSettings); // Error appears in this part
int total = 0;
for (int i = 0; i < _num; i++) {
flutterLocalNotificationsPlugin.schedule(0, 'title', 'body',
DateTime.now().add(Duration(seconds: 5)), platformChannelSpecifics);
total += 1;
}
}
I call the test function like below.
await compute(test, 100000000);
However, I am having the error below:
Exception has occurred.
FlutterError (ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.
If you're running an application and need to access the binary messenger before `runApp()` has been called (for example, during plugin initialization), then you need to explicitly call the `WidgetsFlutterBinding.ensureInitialized()` first.
If you're running a test, you can call the `TestWidgetsFlutterBinding.ensureInitialized()` as the first line in your test's `main()` method to initialize the binding.)
I have indeed followed what was in the error message which is also the same recommendation of others by adding a line of code in the main() body of the Flutter app but was not able to make it work as well.
It seems that I cannot access the plugin from the top-level class used for the Isolate. I'm hoping you guys can help or atleast suggest an improvement on what I am currently doing that may help. Thank you!

Custom notification channel for FCM in flutter

I am trying to customize notification sound of firebase_messaging in flutter. On foreground I am implementing flutter_local_notifications package to deliver notification where I have setup custom sound and vibration. But in case of background, notification is handled by default notification channel. Is there any way I can create a notification channel or use the one I just created using flutter_local_notifications package?
For those of you arriving here because your FCM messages aren't acting as you prefer while your app is in the background:
You will probably need to create a Notification Channel if you want a "heads-up" notification when the app is in the background and you want to have your own custom sound accompanying it. The default Notification Channel used by the FCM does not have the "pop on screen" setting enabled and uses the default system sound. You can see this by going to the app's settings on your device.
OP is using the flutter_local_notifications package, which is pretty much the "go-to" package for notification handling in flutter. You can create your own Notification Channels via the createNotificationChannel method and assign your desired parameters (including sound and priority level). This is the quick and easier way of getting your notifications to act as you want them.
If you want to create your own Notification Channel without the flutter_local_notifications package, then you will have to modify your MainActivity.kt (or Java) file in its native form. It's not overly complicated, but it is more low-level than just using the flutter_local_notifications package. This Medium post describes how to do that (for Android).
In Flutter you can create android notification channel yourself thru MainActivity.kt or MainActivity.java file depending whichever you have in your project Android folder. Good guide here - using MainActivity.kt which is easy and working, tried myself - it works:
package com.example.new_channel //Your own package name
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.net.Uri;
import android.media.AudioAttributes;
import android.content.ContentResolver;
class MainActivity: FlutterActivity() {
private val CHANNEL = "somethinguniqueforyou.com/channel_test" //The channel name you set in your main.dart file
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// Note: this method is invoked on the main thread.
call, result ->
if (call.method == "createNotificationChannel"){
val argData = call.arguments as java.util.HashMap<String, String>
val completed = createNotificationChannel(argData)
if (completed == true){
result.success(completed)
}
else{
result.error("Error Code", "Error Message", null)
}
} else {
result.notImplemented()
}
}
}
private fun createNotificationChannel(mapData: HashMap<String,String>): Boolean {
val completed: Boolean
if (VERSION.SDK_INT >= VERSION_CODES.O) {
// Create the NotificationChannel
val id = mapData["id"]
val name = mapData["name"]
val descriptionText = mapData["description"]
val sound = "your_sweet_sound"
val importance = NotificationManager.IMPORTANCE_HIGH
val mChannel = NotificationChannel(id, name, importance)
mChannel.description = descriptionText
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"+ getApplicationContext().getPackageName() + "/raw/your_sweet_sound");
val att = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
mChannel.setSound(soundUri, att)
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(mChannel)
completed = true
}
else{
completed = false
}
return completed
}
}
And this is with MainActivity java:
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.os.Build;
import android.media.AudioAttributes;
import androidx.core.app.NotificationCompat;
import android.net.Uri;
import android.content.ContentResolver;
this is a code
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(“new_email_arrived_channel”, “My Emailer”, NotificationManager.IMPORTANCE_HIGH);
notificationChannel.setShowBadge(true);
notificationChannel.setDescription(“”);
AudioAttributes att = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
notificationChannel.setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + “://” + getPackageName() + “/raw/bell”), att);
notificationChannel.enableVibration(true);
notificationChannel.setVibrationPattern(new long[]{400, 400});
notificationChannel.setLockscreenVisibility(NotificationCompat.VISIBILITY_PUBLIC);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(notificationChannel);
}
In Flutter side you may need trigger for initiating the process and naming the notification channel. It is from the same source above:
String _statusText = "Waiting...";
final String _finished = "Finished creating channel";
final String _error = "Error while creating channel";
static const MethodChannel _channel =
MethodChannel('somethinguniqueforyou.com/channel_test');
Map<String, String> channelMap = {
"id": "CHAT_MESSAGES",
"name": "Chats",
"description": "Chat notifications",
};
void _createNewChannel() async {
try {
await _channel.invokeMethod('createNotificationChannel', channelMap);
setState(() {
_statusText = _finished;
});
} on PlatformException catch (e) {
_statusText = _error;
print(e);
}
}
Now for all Android versions you need only this format notification payload:
"notification": {
"body": "Test notification",
"title": "Test Test Test",
"click_action": "FLUTTER_NOTIFICATION_CLICK",
"sound": "your_custom_sound"
"android_channel_id": "channel_id_youcreated",
},
'to':
"",
},
Sound file name is not necessary in the above given notification payload if you assigned that sound to your notification channel thru MainActivity.kt or java file. However, it is necessary for older Android versions as they will use the sound file directly.
If you check in the Firebase Console, when sending the notification, you can especify a channel ID in "other options", there you can write the channel you have already created using flutter_local_notifications.
Hope this helps!
Since you are already using flutter_local_notifications there is an alternate way to the implementation mentioned by #Elmar for Android.
As per the FCM Legacy API Doc
The notification's channel id (new in Android O).
The app must create a channel with this channel ID before any
notification with this channel ID is received.
If you don't send this channel ID in the request, or if the channel ID
provided has not yet been created by the app, FCM uses the channel ID
specified in the app manifest.
Step 1: Define android notification channel
/// The plugin
FlutterLocalNotificationsPlugin? flutterLocalNotificationsPlugin;
/// File name should not have the extension
static const String soundFileName = 'file_name_of_sound';
/// Custom notification channel
final channel = const AndroidNotificationChannel(
'custom_notification_channel_01',
'Notification channel with custom sound notifications',
description: 'This channel is used for notifications with a custom sound.',
importance: Importance.high,
playSound: true,
sound: RawResourceAndroidNotificationSound(soundFileName),
);
Step 2: Create notification channel, this should be done early in the code, preferrably where the FirebaseMessaging is initialized
await flutterLocalNotificationsPlugin
?.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
Step 3: Include the android channel id in push notification from the backend.
Now you are good to go, test this one locally using postman or a client of your choice.
METHOD: POST
URL: https://fcm.googleapis.com/fcm/send
HEADER: Don't forget to add Authorization=key=${server_key_from_firebase_console}
BODY:
{
"to": "fcm_token",
"notification": {
"android_channel_id": "custom_notification_channel_01",
"title": "Title of the custom notification",
"body": "An important notification with a custom sound",
"sound": "custom_sound_file_name"
}
}
PS: The sound is optional, if you have multiple custom sounds, enable the play sound in the channel and include the file name of the custom sound in the notification payload.