Use flutter_local_notifications plugin in Isolate - flutter

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!

Related

Error: No named parameter with the name 'onSelectNotification'

class HelperNotification {
static Future<void> initialize(FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin) async {
var androidInitialize = new AndroidInitializationSettings('notification_icon');
var iOSInitialize = new DarwinInitializationSettings();
var initializationsSettings = new InitializationSettings(android: androidInitialize, iOS: iOSInitialize);
flutterLocalNotificationsPlugin.initialize(initializationsSettings, onSelectNotification:(String? payload) async {
try{
if(payload != null && payload.isNotEmpty) {
// Get.toNamed(RouteHelper.getOrderDetailsRoute(int.parse(payload)));
}else {
// Get.toNamed(RouteHelper.getNotificationRoute());
}
}catch (e) {}
return;
});
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true,
badge: true,
sound: true,
);
I Found that there was an update with the local notification package but i was trying to impliment it and was unsuccessful can you help me figure it out?
Since version 10.0.0 of the flutter_local_notifications plugin, they removed onSelectNotification parameter. You can read more about it in their changelog:
Breaking change
callbacks have now been reworked. There are now the
following callbacks and both will pass an instance of the
NotificationResponse class onDidReceiveNotificationResponse: invoked
only when the app is running. This works for when a user has selected
a notification or notification action. This replaces the
onSelectNotification callback that existed before. For notification
actions, the action needs to be configured to indicate the the app or
user interface should be shown on invoking the action for this
callback to be invoked i.e. by specifying the
DarwinNotificationActionOption.foreground option on iOS and the
showsUserInterface property on Android. On macOS and Linux, as there's
no support for background isolates it will always invoke this callback
onDidReceiveBackgroundNotificationResponse: invoked on a background
isolate for when a user has selected a notification action. This
replaces the onSelectNotificationAction callback
Read more here: https://pub.dev/packages/flutter_local_notifications/changelog

Run packages in isolate - Flutter

I'm trying to run some image processing and MLmodel prediction inside isolate for all images in the gallery device. But some packages like FaceDetector(from google_ml_kit), and PhotoGallery, don't let me run inside isolate. The question is, exist any possibility to run everything inside an isolate, like an entire package with these imports?
Without using isolate all the code is working and is kind of fast but the screen freezes when I start to load and process the images.
I also could process one by one image whitout freezing, but takes too much time.
So I m trying to run with isolate for example when a run this code:
void process(String path){
final options = FaceDetectorOptions(
performanceMode: FaceDetectorMode.accurate,
minFaceSize: .4,
);
final faceDetector = FaceDetector(options: options);
var file = File(path);
var image = InputImage.fromFile(file);
var bytes = file.readAsBytesSync();
var faces = (await faceDetector.processImage(image))
.map(
(e) => FaceRect(
e.boundingBox.left,
e.boundingBox.top,
e.boundingBox.width,
e.boundingBox.height,
),
)
.toList();
var faceImage = FacesImage(path, faces, bytes);
}
compute(process, "/storage/emulated/0/WhatsApp/Media/WhatsApp Images/IMG-20221022-WA0006.jpeg");
I get this:
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Binding has not yet been initialized.
E/flutter (32478): The "instance" getter on the ServicesBinding binding mixin is only available once that binding has been initialized.
E/flutter (32478): Typically, this is done by calling "WidgetsFlutterBinding.ensureInitialized()" or "runApp()" (the latter calls the former). Typically this call is done in the "void main()" method. The "ensureInitialized" method is idempotent; calling it multiple times is not harmful. After calling that method, the "instance" getter will return the binding
But I can't call WidgetsFlutterBinding.ensureInitialized() in isolate and my app already have.
There is any alternative to find all photos and albuns that can be called in isolate? and faceDetector?
Run an entire package in isolate is a possibility?

Flutter Background Processes Using Android Alarm Manager and Isolates

I'm trying to get a timer (down to the hundredths of seconds) to work in Flutter even when the app is closed. I initially tried to use isolates as I thought they would work yet after testing with a Pixel 4 running Android 11 I found that it was still not firing correctly when the app was closed. After some googleing I came across Android Alarm Manager and I have everything set up again yet it doesn't appear that the periodic function is firing correctly.
Heres the BLoC map for triggering the counter:
Stream<TimerState> _mapTimerStartedToState(TimerStarted start) async* {
AndroidAlarmManager.initialize();
port.listen((_) async => await _incrementCounter());
startCounter();
print(_counter);
yield TimerRunInProgress(start.duration);
}
Here's the startCounter() function:
void startCounter() async {
prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey(countKey)) {
await prefs.setInt(countKey, 0);
}
IsolateNameServer.registerPortWithName(
port.sendPort,
isolateName,
);
await AndroidAlarmManager.periodic(
Duration(milliseconds: 100),
// Ensure we have a unique alarm ID.
Random().nextInt(pow(2, 31)),
callback,
exact: true,
wakeup: true,
);
}
And then here's my callback:
static Future<void> callback() async {
print('Alarm fired!');
// Get the previous cached count and increment it.
final prefs = await
SharedPreferences.getInstance();
int currentCount = prefs.getInt(countKey);
await prefs.setInt(countKey, currentCount + 1);
// This will be null if we're running in the background.
print(currentCount);
uiSendPort ??= IsolateNameServer.lookupPortByName(isolateName);
uiSendPort?.send(null);
}
Am I on the right path here? Can AndroidAlarmManager do what I'm trying to do? I'm not exactly sure why the isolate approach didn't work on its own either, the only explanation I got was that I needed to use AndroidAlarmManager. Now, the events aren't firing at the 100 ms rate as I told them to and are instead firing 1 to several minutes apart.
Android restricts the frequencies for alarms. You cannot schedule alarms as frequently as 100 milliseconds with AlarmManager.
Please refer the note in red background on : https://developer.android.com/reference/android/app/AlarmManager
Note: Beginning with API 19 (Build.VERSION_CODES.KITKAT) alarm
delivery is inexact: the OS will shift alarms in order to minimize
wakeups and battery use. There are new APIs to support applications
which need strict delivery guarantees; see setWindow(int, long, long,
android.app.PendingIntent) and setExact(int, long,
android.app.PendingIntent). Applications whose targetSdkVersion is
earlier than API 19 will continue to see the previous behavior in
which all alarms are delivered exactly when requested.

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.

Daily recurring notifications in 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.