Reputation: 19288
I'm trying to use the Android alarmmanager with notifications, but I am encounter difficulties. Basically, this is the behaviour I am trying to achieve:
I created some basic example which is available here: https://github.com/robindijkhof/flutter_noti I will include a snippet below for when the repo is deleted.
Problems I am encountering:
I have no clue how to solve this. Also, I have no idea if I am on the right track. I'd appreciate some help.
HOW TO FIX In addition to the accepted answer, I'm using reflection to update AtomicBoolean
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
(call, result) -> {
if (call.method.equals("resetAlarmManager")) {
try {
// Get field instance
Field field = io.flutter.plugins.androidalarmmanager.AlarmService.class.getDeclaredField("sStarted"); // NOTE: this field may change!!!
field.setAccessible(true); // Suppress Java language access checking
// Remove "final" modifier
Field modifiersField = Field.class.getDeclaredField("accessFlags");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// Set value
field.set(null, new AtomicBoolean(false));
} catch (Exception ignored) {
Log.d("urenapp", "urenapp:reflection");
if (BuildConfig.DEBUG) {
throw new RuntimeException("REFLECTION ERROR, FIX DIT.");
}
}
}
});
I'm calling this native function in my notification click callback and when to app starts. This requires me to reschedule all alarms.
SNIPPET
import 'package:android_alarm_manager/android_alarm_manager.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class AlarmHelper {
static final int _REQUEST_CODE = 12377;
static final int _REQUEST_CODE_OVERTIME = 12376;
static void scheduleAlarm() async {
print('schedule');
//Read the desired time from sharedpreferences and/or firebase.
AndroidAlarmManager.cancel(_REQUEST_CODE);
AndroidAlarmManager.oneShot(Duration(seconds: 10), _REQUEST_CODE, clockIn, exact: true, wakeup: true);
}
}
void clockIn() async {
//Do Stuff
print('trigger');
//Read some stuff from sharedpreference and/or firebase.
setNotification();
//Schedule the next alarm.
AlarmHelper.scheduleAlarm();
}
void setNotification() async {
print('notification set');
//Read some more stuff from sharedpreference and/or firebase. Use that information for the notification text.
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
var androidPlatformChannelSpecifics = new AndroidNotificationDetails('test', 'test', 'test',
importance: Importance.Max, priority: Priority.Max, ongoing: true, color: Colors.blue[500]);
var iOSPlatformChannelSpecifics = new IOSNotificationDetails();
var platformChannelSpecifics = new NotificationDetails(androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin
.show(0, 'test', 'time ' + DateTime.now().toIso8601String(), platformChannelSpecifics, payload: 'item id 2');
}
Upvotes: 3
Views: 6040
Reputation: 8714
After a couple of hours of debugging the android_alarm_manager package, the reason for the callback no longer being triggered after closing the app by tapping back and then opening it again seems to be this static boolean on the native side:
// TODO(mattcarroll): make sIsIsolateRunning per-instance, not static.
private static AtomicBoolean sIsIsolateRunning = new AtomicBoolean(false);
This boolean keeps track of whether your Flutter callback has been registered with the alarm service. With the way Android works, when the app is closed by tapping back, the activity is destroyed, but the app memory is not cleared down, so static variables such as the boolean mentioned above keep their values if you open the app again. The value of this boolean is never set back to false, and because of that, even though the plugin registers itself with the application again, and you initialize it again, it doesn't run the part of the code that starts a Flutter isolate that would run your alarm callback:
sBackgroundFlutterView = new FlutterNativeView(context, true);
if (mAppBundlePath != null && !sIsIsolateRunning.get()) { // HERE sIsIsolateRunning will already be true when you open the app the 2nd time
if (sPluginRegistrantCallback == null) {
throw new PluginRegistrantException();
}
Log.i(TAG, "Starting AlarmService...");
FlutterRunArguments args = new FlutterRunArguments();
args.bundlePath = mAppBundlePath;
args.entrypoint = flutterCallback.callbackName;
args.libraryPath = flutterCallback.callbackLibraryPath;
sBackgroundFlutterView.runFromBundle(args);
sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry());
}
Given that the boolean is private, I don't think you can do anything about it and you need to wait for it to be fixed - the TODO above the boolean's declaration indicates that the package's developers might already be aware of potential issues caused by it being static.
As for navigating to a specific page when the notification is tapped:
Create a GlobalKey
for the navigator within _MyAppState
:
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
Add it to your MaterialApp
:
return MaterialApp(
navigatorKey: navigatorKey,
And initialize the flutter_local_notifications plugin within initState()
of _MyAppState
. This way, the onSelectNotification
function you pass to flutterLocalNotificationsPlugin.initialize
can reference your navigatorKey
:
flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: (String payload) {
navigatorKey.currentState.pushNamed("your_route");
});
Upvotes: 3
Reputation: 2832
I can't open a specific page when the notification is clicked.
Set up onSelectNotification
in initState
of your main page. In the handler, you can push to a specific page using the Navigator
. You will need to change MyApp
to a stateful widget.
Future<void> onSelectNotification(String payload) async {
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen(payload)),
);
}
When the app is closed using the back button, the alarmmanager keep firing which is as expected. However, when the notification is clicked, the app opens and the alarmmanager does not fire anymore. Probably because it runs on another Isolate?
Not too sure what is wrong with this, what you can try is to use API from the flutter_local_notifications
package, as it provides the same functionality to achieve what you want.
Upvotes: 0