Question: How to make other plugins compatible with flutter_local_notifications
The alarm plugin also schedules notifications. It thus registers itself as an UNUserNotificationCenterDelegate.
This breaks the flutter_local_notifications notification tap callbacks such as onDidReceiveNotificationResponse on iOS. See https://github.com/gdelataillade/alarm/issues/331.
Do you have any tips on how we update the alarm plugin so that it plays nicely with this one?
I took a look at https://github.com/firebase/flutterfire/pull/1829 but do not understand what to take away from it.
Regarding what I did for https://github.com/firebase/flutterfire/pull/1829/files, I'm not sure what I did there is still in play as I've not looked into that plugin's code in great depth since invertase took over. The goal of that PR though was to ensure the FCM PR only handles notifications that are for FCM. This can be done by identifying the inspecting userInfo[kGCMMessageIDKey].
A solution that comes to mind is to take a similar approach where any plugin that displays notifications populates that dictionary with information unique to that plugin. The plugin can then inspect the dictionary for the existence of such data to determine if it is the plugin that is meant to handle the event (e.g. notification being tapped). I've done it for this plugin based on data it populates to function here. Every time an event occurs, it will do a check to determine if it is meant to handle the notification.
On a related note, years ago I raised https://github.com/flutter/flutter/issues/22099. I'm not proficient with developing within Apple's ecosystem and proposed it as Apple only allows one delegate to be handled. Supposedly a common workaround to rely on some technique called method swizzling. I'm not proficient enough to know how to implement it but it seemed like Flutter SDK had already a precedence of providing an alternate (see here) so developers don't have to have worry about it. I was hoping Flutter itself could take the same approach for UNUserNotificationCenterDelegate calls. There could be opportunity for Flutter to also determine if a plugin is meant to handle a specific delegate call as part of this too. I haven't checked to see if Flutter does this for app delegate calls or just simply fires the delegate methods for all plugins but both should behave consistently. Strictly speaking with what you showed for the alarm plugin and what is also in the docs for this plugin, the code indicates FlutterAppDelegate registers itself as an UNUserNotificationCenterDelegate. It's not actually a plugin that is doing so yet somehow plugins are able to receive the delegate calls. This seems to be relying on some undocumented behaviour that is also inconsistent with app delegate calls are handled. I noticed your profile mentions you work at Google. If that's still up to date, do you by chance have connections with the Flutter team to see if this can be revisited?
Thanks for the pointers @MaikuB ; I was able to resolve the conflict by:
- Ensuring AppDelegate.swift registers the app as an UNUserNotificationCenterDelegate
- When overriding
userNotificationCentermethods always check if the notification is meant for the plugin, and if notreturn
The relevant commit that implements the steps above can be found here: https://github.com/gdelataillade/alarm/commit/e1b27b56bebf3f26323daeeb33077195e20b298e
flutterfire also stores the existing delegate method override and calls the other handler as well: https://github.com/firebase/flutterfire/blob/a2246cd0ae8a7a53abc2537d7cd66ee079d3b096/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging/Sources/firebase_messaging/FLTFirebaseMessagingPlugin.m#L282
I'm not sure if this is required or if iOS FlutterPlugin class handles calling all overriding methods.
The FlutterPlugin implementation calls the handler of all registered plugins in a loop: https://github.com/flutter/flutter/blob/45b21ec3bbb37c2ac067218939c995318e4404a3/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm#L292-L315
Thus, storing and calling the original userNotificationCenter handlers, like flutterfire does, is not required.
Plugin authors only need to be careful not to override the UNUserNotificationCenter delegate. In other words, never use a clause like UNUserNotificationCenter.current().delegate = self.
Re: "There could be opportunity for Flutter to also determine if a plugin is meant to handle a specific delegate call"
This would require the FlutterPlugin iOS class to intercept calls to UNUserNotificationCenter.current().add(...) and attach an identifier, such as a special userInfo entry. I don't know Obj-C or Swift well enough, but I'd assume intercepting systems calls is not easy to do.
Alternatively, FlutterPlugin could host a seperate endpoint to relay notification creation requests and educate plugin authors to use that instead.
However, at that point, I think it would be easier to educate plugin authors to identify and disregard userNotificationCenter calls that are not meant for them.
In any case, this behaviour should be documented and ideally linked to from https://docs.flutter.dev/packages-and-plugins/developing-packages.
This would require the FlutterPlugin iOS class to intercept calls to UNUserNotificationCenter.current().add(...) and attach an identifier, such as a special userInfo entry. I don't know Obj-C or Swift well enough, but I'd assume intercepting systems calls is not easy to do.
Thanks for the response. To clarify, attaching an identifier was in addition to what I mentioned on Flutter providing an API to allow a plugin to register itself as an UNUserNotificationCenterDelegate. In the absence of this, given Flutter has other APIs, it would make sense that a similar API called, say, addUNUserNotificationCenterDelegate is added to assign with the other APIs available. Presumably, what Flutter does behind the scenes for the other APIs is maintain a collection of all plugins that have registered and the engine invokes the appropriate delegate method in response. The API could do the same without attaching the identifier and leave it to plugins to respond as needed.
Either way, I agree that the documentation for developing plugins is lacking info
IIUC plugins doen't need register themselves as UNUserNotificationCenterDelegate. All plugins that inherit from FlutterPlugin (example) are already considered UNUserNotificationCenterDelegates and their completionHandler is called here.
Sorry didn't end up coming back this. I'll close this as it looks like you were able to solve the issue.
Regarding what you shared in your last message, thanks for that info as it explains how the Flutter engine works. Looks like it requires plugins to be added as an application delegate for the engine to fire a plugin's notification delegate methods. Both delegates are for different purposes so I'm not sure why this decision was made. This almost tripped me over as I was looking at these docs on migrating to the UISceneDelegate. I tried to remove registering the plugin as an app delegate as thought I forgot to clean that up. I found this affected the notification callbacks but I recalled your comment with the linked code. Thanks for the save 😅