MaikuB / flutter_local_notifications

A Flutter plugin for displaying local notifications on Android, iOS, macOS and Linux
2.44k stars 1.39k forks source link

Support for custom notification actions #17

Closed MaikuB closed 1 year ago

MaikuB commented 6 years ago

The plugin should provide the ability to specify custom notification actions. However, this will depends on the Flutter engine being able to support headless-Dart code as per https://github.com/flutter/flutter/issues/6192 and https://github.com/flutter/flutter/issues/3671

It appears Android support is there but will wait to see on if the engine can support it for iOS applications, and for Flutter to provide abstractions to access the functionality. Without the support being added in, then this won't work for scenarios like when the application has been terminated as the logic associated with the action would've been defined in Dart.

Update with remaining work (IMO)

Note that last two perhaps could be omitted given market share and those require using deprecated APIs

Edit:

AbdulMalikDev commented 3 years ago

@hanyska it is still not the functionality in my app, this is the functionality that I am trying to understand if the issue will add support for (at list part of it). The screen shot is example of the same behavior that currently can be found in google alarm clock.

Use awesome_notifications package in flutter. It has better notification capabilities and it is providing action buttons also.

guyluz11 commented 3 years ago

@AbdulMalikDev What are the benefit and disadvantages of it compared to this library ?

AbdulMalikDev commented 3 years ago

@AbdulMalikDev What are the benefit and disadvantages of it compared to this library ?

Please check on pub.dev it has a lot of customisation possibilities

alistairholmes commented 3 years ago

@MaikuB I was wondering how it's possible for awesome_notifications package to show action buttons for a notification. Have things changed on the Flutter side? Or is that package using some native code behind the scenes?

P-B1101 commented 3 years ago

Any update?!

rafaelsetragni commented 3 years ago

@MaikuB I was wondering how it's possible for awesome_notifications package to show action buttons for a notification. Have things changed on the Flutter side? Or is that package using some native code behind the scenes?

And im keeping wondering why you insist on using this plugin if you have awesome_notifications plugin ready to use? If there is some missing feature, i invite everyone to implement it and ill give to you guys all the suport and orientations to develop it.

alicankaramil commented 3 years ago

@rafaelsetragni they are literally just asking a question, check your attitude mate. This is not a playground, providing something online doesn't give you the right to act like this.

jonas-zebari commented 3 years ago

The plugin awesome_notifications does technically have this functionality, but it is not ready to use for production or any app that is integrated with firebase/cloud messaging. I think having the option to give a local notification an action with a callback makes perfect sense for this package. I am not able to write this code for Android or iOS for lack of knowledge and experience, but I would be happy with work with anyone who might be able to do so.

rafaelsetragni commented 3 years ago

In fact, there are already many production mode apps that use awesome_notifications. But as I'm still in the development process, I've chosen not to move the versions to 1.0.0 and beyond because I can make some significant architectural changes along the way, as I will in version 0.0.7.

About firebase_messaging, it's not possible to perfectly integrate firebase through the "Flutter layer" with all the awesome_notifications features. You only need to use what firebase_messaging provides to build the push notifications and awesome_notifications to build only local notifications, and also there is some performance issues about that, because dart code execution always involves starting a Flutter engine (sort of a JVM for Dart), which is a heavy task for the device.

I'm also developing a companion plugin to integrate firebase and awesome_notification seamlessly into the native layer, with all the awesome_notification features available remotely. This way everything about this integration will be done at the native level, making it possible to achieve maximum performance.

If everything is working fine, this will be the last release of the series in development mode.

jonas-zebari commented 3 years ago

I specifically meant that it is not ready for production apps which also integrate with firebase_messaging (as I stated) as having both plugins active causes many issues. I am eagerly waiting for the companion app you mention (though i can't seem to find the repo anymore) and I'm wondering if there is anything I can do to help? I do not know what your plans are for the 0.0.7 release and the companion plugin. If not, what kind of timeline would you estimate for the releases you mentioned?

rafaelsetragni commented 3 years ago

For now, I just need to finish transcribing the background process for iOS and test what has been done for Android devices. I can estimate the release of version 0.0.7 in two weeks and the complementary plugin in 2 months.

For now, no help is needed to finish these versions, but I'll keep you in touch if I find something that you can help with.

hatemragab commented 2 years ago

Any update?

mrtnetwork commented 2 years ago

For now, I just need to finish transcribing the background process for iOS and test what has been done for Android devices. I can estimate the release of version 0.0.7 in two weeks and the complementary plugin in 2 months.

For now, no help is needed to finish these versions, but I'll keep you in touch if I find something that you can help with.

:D what's happened?

rafaelsetragni commented 2 years ago

For now, I just need to finish transcribing the background process for iOS and test what has been done for Android devices. I can estimate the release of version 0.0.7 in two weeks and the complementary plugin in 2 months. For now, no help is needed to finish these versions, but I'll keep you in touch if I find something that you can help with.

:D what's happened?

That happened:

image

I gonna push Awesome Notifications to the next level, achieving 90%+ covered test cases and automate it with GitHub.

MaikuB commented 2 years ago

Thanks to @ened and @Kavantix, the 10.0.0-dev.2 prerelease is now available. This supports actions on Android, iOS and macOS. If the community could use this release to this test, provide feedback and contribute with updates to docs, fixes etc then that would much appreciated.

@proninyaroslav would you by chance be able to help with the Linux side? The way actions are defined are platform-specific. From memory, the original PR you submitted left them out to see if there was going to be a common API for all platforms

proninyaroslav commented 2 years ago

@MaikuB Ok, I'm on it.

MaikuB commented 2 years ago

Thanks @proninyaroslav, much appreciated for this. FYI, I'm doing releases from the 10.0.0 branch so if you plain to make additions, best to do it from there. I merged the PR for actions to master to make it easier to deal with doing other changes. In hindsight, I could've merged it to a different branch but should be fine

proninyaroslav commented 2 years ago

@MaikuB Done https://github.com/MaikuB/flutter_local_notifications/pull/1442

alesp commented 2 years ago

I'm testing actions on Android, and NotificationActionDetails sometimes contains an old payload in onSelectNotificationAction. If I add PendingIntent.FLAG_UPDATE_CURRENT to the following code in FlutterLocalNotifications.java:

  final PendingIntent actionPendingIntent =
            PendingIntent.getBroadcast(
                context, requestCode++, actionIntent, PendingIntent.FLAG_ONE_SHOT);

it properly updates payload every time.

Kavantix commented 2 years ago

@alesp so you use PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT?

@MaikuB I've also ran into a few issues:

MaikuB commented 2 years ago

@Kavantix could it both those issues you mentioned are related since the receiver wouldn't be firing or did you also run into the problem after you added the exported flag on your own fork

Kavantix commented 2 years ago

@MaikuB I didnt add the flag yet, I just set the targetSdk to 30 and then the actions worked as long as the app was running, so it might be possible that they are related and are both related to android 12. However it is still weird that that worked before

MaikuB commented 2 years ago

Ok also regarding that payload issue, if I'm not mistaken, that would suggest request codes for notification actions and the notification IDs are clashing. If so, I'm starting to wonder if request code should be something that is set by as part of using the plugin's APIs. Not ideal but not sure what other approaches are available

Kavantix commented 2 years ago

@MaikuB Actually now that you mention this, if they clash that might also influence the starting of the isolate 🤔

Kavantix commented 2 years ago

The flag doesnt resolve the second issue, when I click on an action from the example app after killing the app I get this error in logcat:

2022-01-22 00:20:28.479 7298-7298/com.dexterous.flutter_local_notifications_example E/cations_exampl: No implementation found for io.flutter.view.FlutterCallbackInformation io.flutter.embedding.engine.FlutterJNI.nativeLookupCallbackInformation(long) (tried Java_io_flutter_embedding_engine_FlutterJNI_nativeLookupCallbackInformation and Java_io_flutter_embedding_engine_FlutterJNI_nativeLookupCallbackInformation__J)
2022-01-22 00:20:28.480 7298-7298/com.dexterous.flutter_local_notifications_example D/AndroidRuntime: Shutting down VM
2022-01-22 00:20:28.480 7298-7298/com.dexterous.flutter_local_notifications_example E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.dexterous.flutter_local_notifications_example, PID: 7298
    java.lang.UnsatisfiedLinkError: No implementation found for io.flutter.view.FlutterCallbackInformation io.flutter.embedding.engine.FlutterJNI.nativeLookupCallbackInformation(long) (tried Java_io_flutter_embedding_engine_FlutterJNI_nativeLookupCallbackInformation and Java_io_flutter_embedding_engine_FlutterJNI_nativeLookupCallbackInformation__J)
        at io.flutter.embedding.engine.FlutterJNI.nativeLookupCallbackInformation(Native Method)
        at io.flutter.view.FlutterCallbackInformation.lookupCallbackInformation(FlutterCallbackInformation.java:28)
        at com.dexterous.flutterlocalnotifications.isolate.IsolatePreferences.lookupDispatcherHandle(IsolatePreferences.java:43)
        at com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver.startEngine(ActionBroadcastReceiver.java:118)
        at com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver.onReceive(ActionBroadcastReceiver.java:84)
        at android.app.ActivityThread.handleReceiver(ActivityThread.java:4348)
        at android.app.ActivityThread.access$1600(ActivityThread.java:256)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2101)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7842)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Kavantix commented 2 years ago

And just confirmed that indeed on my older branch it works. It looks like this breaks on the code that was added to support the isolate name server if I'm correct. @ened do you have any ideas?

MaikuB commented 2 years ago

@Kavantix the isolate name server code is something I added that only involves Dart code and is specific to the example app to demonstrate how to have the background isolate communicate with the main one

MaikuB commented 2 years ago

@Kavantix the issue you mentioned should be solved now. It happened as the engine is meant to be initialised first before trying to look up the callback handler

AdamBridges commented 2 years ago

@MaikuB Do you think it's feasible to expect an option/parameter for an Android notification action to be capable of launching the app / bringing it to the foreground when tapped? Perhaps a check that issues the same logic that launches the app when a notification is selected?

Investigated and it certainly is feasible: Looks like a simple parameter check under FlutterLocalNotificationsPlugin.java could be implemented for each actionPendingIntent value to replace the actionIntent with the default pendingIntent and as an Activity, like so final PendingIntent actionPendingIntent = PendingIntent.getActivity(context, requestCode++, pendingIntent, actionFlags). It's not the best solution since it will likely call whatever method is set to a given notification's onSelectNotification callback, but it's quick and will deliver a solution I'm sure many want. I would try to do this myself but this is new territory for me and it's already taken me a couple hours just to figure this much out.

Workarounds with android_intent_plus is not an option as of Android 12, at least not with flutter_local_notifications 10.0.0-dev.6, sadly.

MaikuB commented 2 years ago

@AdamBridges I agree it makes sense to achieve the outcome that you mention. What are the issues with android_intent_plus and Android 12? I recently addressed the issue with notification actions in Android 12 and I haven't heard of Android 12 issues with the android_intent_plus unless I've missed them.

Regarding the intent issue you mention and how it would call the wrong callback, that could be solved by using a different intent action. The plugin can check the action on the intent to determine what it should do. This would be similar to what is done at https://github.com/MaikuB/flutter_local_notifications/blob/f126ddb781d6df88e3c89a93962a123153f107b8/flutter_local_notifications/android/src/main/java/com/dexterous/flutterlocalnotifications/FlutterLocalNotificationsPlugin.java#L1640

This leads me to believe the plugin may require more changes where potentially there are two callbacks when tapping on an action and potentially a breaking change to current behaviour. The resulting approach is similar to how the FCM plugin handles foreground and background messages

  1. the current callback in the prerelease gets renamed (e.g. onSelectBackgroundNotificationAction) to indicate that it's only for "background" notification actions as they won't trigger the app to appear
  2. this leaves aonSelectNotificationAction callback that behaves differently and is only used for "foreground" notification actions where if app is already running, it triggers this callback that runs the main isolate. This would be similar to how the onSelectNotification works and allows apps to use this data to update the UI e.g. navigate the user to another page.

Regarding second callback/point, there'd need to be a mechanism to figure out if a notification action launched the app much how getNotificationAppLaunchDetails works. This could be a new method.

Note however, this is something that will require the community to contribute to right now. I can assist in guidance but implementing this is going to need others to step up and assist. It's one of the reasons why there's the "needs help" label here and the plugin already suffers from a lack of contribution from community e.g. a feature request is made with the relevant code changes mentioned in a snippet but author doesn't submit a PR when asked.

One might how argue how much time it's taken to figure things out but one thing that is being neglected is how much time have has been saved by using something that's offer freely? FYI that right now, this plugin is currently worked on it my spare time and I'm essentially working on something that I don't even use. Therefore I kindly ask that if any of the community here is interested in features etc that they help out to help combat sustainability in open-source.


On a semi-related note: FYI that this issue used to be blocked to see if the Flutter team would tackle having a process wide isolate as without it, this would require two callbacks and background isolates use shared preferences. Trying to see if this would be tackled within Flutter was to reduce amount of code that would need to be maintained and avoid potential headaches in trying to figure out a migration path if two callbacks were provided and then the Flutter team implemented a process-wide isolate. However it's been a number of years since that issue was raised with no progress so I think it's fair to say it isn't going to be tackled

AdamBridges commented 2 years ago

@MaikuB Okay, I created a PR. It's my first time doing this so hopefully I got it right.

rafaelsetragni commented 2 years ago

@MaikuB Do you think it's feasible to expect an option/parameter for an Android notification action to be capable of launching the app / bringing it to the foreground when tapped? Perhaps a check that issues the same logic that launches the app when a notification is selected?

Investigated and it certainly is feasible: Looks like a simple parameter check under FlutterLocalNotificationsPlugin.java could be implemented for each actionPendingIntent value to replace the actionIntent with the default pendingIntent and as an Activity, like so final PendingIntent actionPendingIntent = PendingIntent.getActivity(context, requestCode++, pendingIntent, actionFlags). It's not the best solution since it will likely call whatever method is set to a given notification's onSelectNotification callback, but it's quick and will deliver a solution I'm sure many want. I would try to do this myself but this is new territory for me and it's already taken me a couple hours just to figure this much out.

Workarounds with android_intent_plus is not an option as of Android 12, at least not with flutter_local_notifications 10.0.0-dev.6, sadly.

@AdamBridges I did exactly that in Awesome Notification's 0.7.0 beta.

noinskit commented 2 years ago

Could you please provide a status update? It seems that 10.0 with notification actions is very close and I'm eager to switch (from my in-house flnp fork with actions support). What are the blockers?

MaikuB commented 2 years ago

@noinskit i've updated the original post on what's remaining from my perspective. Link for convenience is https://github.com/MaikuB/flutter_local_notifications/issues/17#issue-316443725. PRs and feedback would be welcome, I remember you did submit one in the past. From memory, dealing with the foreground notification actions should be similar to what you worked on back then. Currently what's in 10.0 invokes logic that runs in the background via isolates for headless execution

MaikuB commented 2 years ago

10.0.0-dev.12 has been released where the callbacks have been reworked and changes done around specifying notification categories. This is a breaking change and I have no plans to work on adding support for actions on older versions of iOS and macOS myself so if anyone is interested in support for those OS versions, then they'd need to look at submitting a PR. This may mean this is the last prerelease but I must stress that moving this to a stable release is contingent on the community helping to test, provide feedback and, ideally, contribute fixes

AdamBridges commented 2 years ago

Good work, @MaikuB!

Sorry I haven't been able to help yet. I thought I would've had my responsibilities done before the end of March but I underestimated the challenges I had before me. Anyway, I'll try out the updates soon and follow up with any issues and, hopefully, contributions.

AdamBridges commented 2 years ago

10.0.0-dev.13 is working well on Android 12 API 31 so far and now that showUserInterface is working as intended, it pretty much solves my original issue of having an action bring the app to the foreground, so cheers for that. I'll follow up again once I get a chance to test on iOS to share how that works out.

MaikuB commented 2 years ago

Thanks for the feedback @AdamBridge and keep me posted. I've not seen more feedback from others though so not sure if that's something to be concerned about...

MaikuB commented 2 years ago

@AdamBridges have you had a chance to test to on iOS? From the scenarios covered in the example, I believe this should work for your scenarios too

AdamBridges commented 2 years ago

@AdamBridges have you had a chance to test to on iOS? From the scenarios covered in the example, I believe this should work for your scenarios too

Not yet. Hopefully before the end of the August I'll have a chance and will update then.

rich-j commented 2 years ago

@MaikuB we're using this package in our projects and want to start using notification actions. Actions work on Android but crash on iOS.

On a real iPhone Xs running iOS 15.5 we immediately hard crash (i.e. app is terminated) when clicking on the notification action. The Flutter console shows:

*** Assertion failure in -[FlutterEngineManager startEngineIfNeeded:registerPlugins:], FlutterEngineManager.m:73
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'failed to set registerPlugins'
*** First throw call stack:
(0x1db3a9288 0x1f40a3744 0x1dcc36360 0x10077c674 0x1db00ee6c 0x1db010a30 0x1db01ef48 0x1db01eb98 0x1db361800 0x1db31b704 0x1db32ebc8 0x1f7462374 0x1ddc9e648 0x1dda1fd90 0x10052cf78 0x100925ce4)
libc++abi: terminating with uncaught exception of type NSException
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x0000000212de5b38 libsystem_kernel.dylib`__pthread_kill + 8
libsystem_kernel.dylib`__pthread_kill:
->  0x212de5b38 <+8>:  b.lo   0x212de5b58               ; <+40>
    0x212de5b3c <+12>: pacibsp 
    0x212de5b40 <+16>: stp    x29, x30, [sp, #-0x10]!
    0x212de5b44 <+20>: mov    x29, sp
Target 0: (Runner) stopped.
Lost connection to device.

We do see the notification and the action choice. We can successfully click on the notification itself and receive the appropriate callback. It's possible that we are missing something in our initialization. To do further testing, we created a new Flutter demo app using flutter_local_notifications: ^10.0.0-dev.16 and added the following code to the app:

main.dart ```dart import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); _initNotification(); runApp(const MyApp()); } Future _initNotification() async { final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); final AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); final DarwinInitializationSettings initializationSettingsIOS = DarwinInitializationSettings( onDidReceiveLocalNotification: didReceiveLocalNotification, // Only needed for iOS <10.0 (our minimum) notificationCategories: [ DarwinNotificationCategory("iosActions", actions: [DarwinNotificationAction.plain("theAction", "Do the action")]) ]); final InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS); await flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: didReceiveNotificationResponse, onDidReceiveBackgroundNotificationResponse: didReceiveBackgroundNotificationResponse, ); const AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails( 'test_channel_id', 'Test Channel Name', channelDescription: 'Notification test channel', importance: Importance.max, priority: Priority.high, actions: [AndroidNotificationAction("theAction", "Do the action")], ); const DarwinNotificationDetails iosPlatformChannelSpecifics = DarwinNotificationDetails( subtitle: "subTitleText", threadIdentifier: "threadId", categoryIdentifier: "iosActions", ); const NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics, iOS: iosPlatformChannelSpecifics); await flutterLocalNotificationsPlugin .show(1, "Action Test", "Notification with action", platformChannelSpecifics, payload: "Payload stuff") .catchError((error) => print("Notification failed $error")); } //---------- void didReceiveNotificationResponse(NotificationResponse details) => print("Received DidReceiveNotificationResponse ${details.id}, ${details.actionId}, " "${details.notificationResponseType}, ${details.payload}"); void didReceiveBackgroundNotificationResponse(NotificationResponse details) => print("Received DidReceiveBackgroundNotificationResponse ${details.id}, ${details.actionId}, " "${details.notificationResponseType}, ${details.payload}"); // This callback is only for iOS <10 so we shouldn't need it void didReceiveLocalNotification(int id, String? title, String? body, String? payload) => print("Received DidReceiveLocalNotification $id, $title"); //---------- class MyApp extends StatelessWidget { ... the rest of the Flutter Demo ... ```
AppDelegate.swift addition ```swift if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate } ```

When we run the above code on Android we're given a notification with action. We can click on the action and receive this in the console:

I/flutter (21426): Received DidReceiveBackgroundNotificationResponse 1, theAction, NotificationResponseType.selectedNotificationAction, Payload stuff

We can also click on the notification itself (i.e. not the action) and receive:

I/flutter (21426): Received DidReceiveNotificationResponse 1, null, NotificationResponseType.selectedNotification, Payload stuff

On iOS we can click on the notification and receive (so the notification itself is working):

flutter: Received DidReceiveNotificationResponse 1, null, NotificationResponseType.selectedNotification, Payload stuff

When we click on the iOS notification action we crash as shown above. We can also run on the iOS simulator which gives us a detailed crash report (over 500 lines) - here's the relevant stack traces:

Crash stack trace synopsis ``` Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Exception Note: EXC_CORPSE_NOTIFY Triggered by Thread: 0 Last Exception Backtrace: 0 CoreFoundation 0x10e2c75f4 __exceptionPreprocess + 226 1 libobjc.A.dylib 0x10e177a45 objc_exception_throw + 48 2 Foundation 0x10ece3874 _userInfoForFileAndLine + 0 3 flutter_local_notifications 0x10e083d02 __60-[FlutterEngineManager startEngineIfNeeded:registerPlugins:]_block_invoke + 290 (FlutterEngineManager.m:73) 4 libdispatch.dylib 0x10f7228e4 _dispatch_call_block_and_release + 12 5 libdispatch.dylib 0x10f723b25 _dispatch_client_callout + 8 6 libdispatch.dylib 0x10f731043 _dispatch_main_queue_drain + 1050 7 libdispatch.dylib 0x10f730c1b _dispatch_main_queue_callback_4CF + 31 8 CoreFoundation 0x10e233ed5 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9 9 CoreFoundation 0x10e22e6ca __CFRunLoopRun + 2761 10 CoreFoundation 0x10e22d704 CFRunLoopRunSpecific + 562 11 GraphicsServices 0x114810c8e GSEventRunModal + 139 12 UIKitCore 0x12469265a -[UIApplication _run] + 928 13 UIKitCore 0x1246972b5 UIApplicationMain + 101 14 Runner 0x10ddb222f main + 63 (AppDelegate.swift:5) 15 dyld_sim 0x10dfd2f21 start_sim + 10 16 dyld 0x11ac0051e start + 462 Thread 24:: FlutterLocalNotificationsIsolate.2.ui 0 Flutter 0x1120c312f dart::Utf8::CodeUnitCount(unsigned char const*, long, dart::Utf8::Type*) + 63 1 Flutter 0x1121bc1d3 dart::String::FromUTF8(unsigned char const*, long, dart::Heap::Space) + 35 2 Flutter 0x1123c7f31 dart::kernel::KernelReaderHelper::GetSourceFor(long) + 289 3 Flutter 0x11214d90f dart::kernel::KernelLoader::LoadScriptAt(long, dart::DirectChainedHashMap*) + 239 4 Flutter 0x11214c8ce dart::kernel::KernelLoader::InitializeFields(dart::DirectChainedHashMap*) + 1758 5 Flutter 0x11214c9ae dart::kernel::KernelLoader::LoadEntireProgram(dart::kernel::Program*, bool) + 94 6 Flutter 0x11243a8df Dart_LoadLibraryFromKernel + 351 7 Flutter 0x1120ac9b2 flutter::DartIsolate::LoadKernel(std::__1::shared_ptr, bool) + 442 8 Flutter 0x1120acae1 flutter::DartIsolate::PrepareForRunningFromKernel(std::__1::shared_ptr, bool, bool) + 235 9 Flutter 0x1120b49c4 flutter::KernelIsolateConfiguration::DoPrepareIsolate(flutter::DartIsolate&) + 122 10 Flutter 0x1120aa25a flutter::DartIsolate::CreateRunningRootIsolate(flutter::Settings const&, fml::RefPtr, std::__1::unique_ptr >, flutter::DartIsolate::Flags, std::__1::function, std::__1::function const&, std::__1::function const&, std::__1::optional, std::__1::allocator > >, std::__1::optional, std::__1::allocator > >, std::__1::vector, std::__1::allocator >, std::__1::allocator, std::__1::allocator > > > const&, std::__1::unique_ptr >, flutter::UIDartState::Context const&, flutter::DartIsolate const*) + 752 11 Flutter 0x1120b68aa flutter::RuntimeController::LaunchRootIsolate(flutter::Settings const&, std::__1::function, std::__1::optional, std::__1::allocator > >, std::__1::optional, std::__1::allocator > >, std::__1::vector, std::__1::allocator >, std::__1::allocator, std::__1::allocator > > > const&, std::__1::unique_ptr >) + 580 12 Flutter 0x111f31c84 flutter::Engine::Run(flutter::RunConfiguration) + 400 13 Flutter 0x111f4996f std::__1::__function::__func const&)::$_7>, std::__1::allocator const&)::$_7> >, void ()>::operator()() + 119 14 Flutter 0x111e46a1c fml::MessageLoopImpl::FlushTasks(fml::FlushType) + 164 15 Flutter 0x111e4cb98 fml::MessageLoopDarwin::OnTimerFire(__CFRunLoopTimer*, fml::MessageLoopDarwin*) + 26 16 CoreFoundation 0x10e234d6e __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20 17 CoreFoundation 0x10e23483c __CFRunLoopDoTimer + 915 18 CoreFoundation 0x10e233dfd __CFRunLoopDoTimers + 265 19 CoreFoundation 0x10e22e3e1 __CFRunLoopRun + 2016 20 CoreFoundation 0x10e22d704 CFRunLoopRunSpecific + 562 21 Flutter 0x111e4ccd5 fml::MessageLoopDarwin::Run() + 65 22 Flutter 0x111e4692c fml::MessageLoopImpl::DoRun() + 22 23 Flutter 0x111e4baff void* std::__1::__thread_proxy >, fml::Thread::Thread(std::__1::function const&, fml::Thread::ThreadConfig const&)::$_0> >(void*) + 187 24 libsystem_pthread.dylib 0x7fff701cb4e1 _pthread_start + 125 25 libsystem_pthread.dylib 0x7fff701c6f6b thread_start + 15 ```

Please let us know if we're missing any configuration or other processing.

Kavantix commented 2 years ago

@rich-j take a look at the readme on the 10.0.0 branch: https://github.com/MaikuB/flutter_local_notifications/blob/6df12390836fc0fac97badb9227a68fee519e9c6/flutter_local_notifications/README.md?plain=1#L351

rich-j commented 2 years ago

@Kavantix thank you for the pointer to the correct README.md. I have added the suggested code to my AppDelegate.swift and now receive a compile time error of

/IdeaProjects/notification_test/ios/Runner/AppDelegate.swift:11:5: error: cannot find 'FlutterLocalNotificationsPlugin' in scope
        FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in

Here's my updated AppDelegate.swift

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // This is required to make any communication available in the action isolate.
    FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in
      GeneratedPluginRegistrant.register(with: registry)
    }
    GeneratedPluginRegistrant.register(with: self)
    // Following line is support for FlutterLocalNotifications
    if #available(iOS 10.0, *) {
      UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
    }
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Is there an import needed?

Kavantix commented 2 years ago

@rich-j you can import flutter_local_notifications

rich-j commented 2 years ago

@Kavantix thanks, that works

mk-dev-1 commented 2 years ago

Hi guys,

lately I am receiving the following crash notification via Crashlytics:

Non-fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: type 'Null' is not a subtype of type 'int'. Error thrown null.
       at MethodChannelFlutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(platform_flutter_local_notifications.dart:65)
       at FlutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(flutter_local_notifications_plugin.dart:199)
       at NotificationManager.init(notification_manager.dart:111)

Here's whats happening in NotificationsManager.init():

    notificationManager = FlutterLocalNotificationsPlugin();
    await notificationManager!.initialize(
      InitializationSettings(
        android: const AndroidInitializationSettings(
          '@mipmap/ic_launcher_foreground',
        ),
        iOS: DarwinInitializationSettings(
          requestSoundPermission: false,
          requestBadgePermission: false,
          requestAlertPermission: false,
          notificationCategories: [
            DarwinNotificationCategory(
              await darwinNotificationCategoryId(NotificationType.rem),
              actions: <DarwinNotificationAction>[
                DarwinNotificationAction.plain(
                    NotificationAction.snooze.toString(),
                    localizations.notificationActionSnooze(snoozeDuration)),
              ],
              options: <DarwinNotificationCategoryOption>{
                DarwinNotificationCategoryOption.allowAnnouncement,
              },
            ),
            DarwinNotificationCategory(
              await darwinNotificationCategoryId(NotificationType.med),
              actions: <DarwinNotificationAction>[
                DarwinNotificationAction.plain(
                    NotificationAction.take.toString(),
                    localizations.notificationActionTake),
                DarwinNotificationAction.plain(
                    NotificationAction.snooze.toString(),
                    localizations.notificationActionSnooze(snoozeDuration)),
              ],
              options: <DarwinNotificationCategoryOption>{
                DarwinNotificationCategoryOption.allowAnnouncement,
              },
            ),
          ],
        ),
      ),
      onDidReceiveNotificationResponse: (response) async {
        if (response.payload != null) {
          handleLocalNotification(response.payload!);
        }
      },
      onDidReceiveBackgroundNotificationResponse:
          onDidReceiveBackgroundNotificationResponse,
    );

    NotificationAppLaunchDetails? launchDetails =
        await notificationManager!.getNotificationAppLaunchDetails(); // <--- This is where the error occurs
    if (launchDetails != null && launchDetails.didNotificationLaunchApp) {
      if (launchDetails.notificationResponse?.payload != null) {
        handleLocalNotification(launchDetails.notificationResponse!.payload!);
      }
    }

The method behind that on Android is the following:

  @override
  Future<NotificationAppLaunchDetails?>
      getNotificationAppLaunchDetails() async {
    final Map<dynamic, dynamic>? result =
        await _channel.invokeMethod('getNotificationAppLaunchDetails');
    final Map<dynamic, dynamic>? notificationResponse =
        result != null && result.containsKey('notificationResponse')
            ? result['notificationResponse']
            : null;
    return result != null
        ? NotificationAppLaunchDetails(
            result['notificationLaunchedApp'],
            notificationResponse: notificationResponse == null
                ? null
                : NotificationResponse(
                    id: notificationResponse['notificationId'],
                    actionId: notificationResponse['actionId'],
                    input: notificationResponse['input'],
                    notificationResponseType: NotificationResponseType.values[ // <--- Error seems to be on this line
                        notificationResponse['notificationResponseType']],
                    payload: notificationResponse.containsKey('payload')
                        ? notificationResponse['payload']
                        : null,
                  ),
          )
        : null;
  }

I yet have to find a way to replicate that error myself, however this crash started occurring when I bumped flutter_local_notifications from 10.0.0-dev.11 to 10.0.0-dev.14, and I believe the error was essentially introduced with the changes in 10.0.0-dev.12.

Is this happening to anyone else? Any ideas?

AdamBridges commented 2 years ago
if (launchDetails.notificationResponse?.payload != null) {
        handleLocalNotification(launchDetails.notificationResponse!.payload!);
      }

I think the problem may have to do with that line and your use of the Bang (!) operator. Try assigning launchDetails.notificationResponse to a variable and using that to check for null first such as this:

NotificationResponse? response = launchDetails.notificationResponse; 
if (response != null && response.payload != null) {
handleLocalNotification(response.payload);
}

@mk-dev-1

MaikuB commented 2 years ago

@mk-dev-1 is this something you've been able to reproduce yourself? Looks odd that it's happened as the plugin should be returning a notificationResponseType from the Android side. There is something I can try out to see if it fixes the issue but be great if you're able to reproduce it so we can see if the change fixes it

mk-dev-1 commented 2 years ago

@AdamBridges Thank you for looking at it, although I can't see that this should resolve the issue as the error log clearly points to the line before...

@MaikuB Unfortunately I still haven't found a way to reproduce the issue, but it keeps happening in production. From the native Android code in the plugin I also can't really see why that error occurs in the first place. What are you thinking? Maybe I can just incorporate whatever you have in mind in a test version that I distribute to a couple of test users that are affected....

MaikuB commented 2 years ago

@mk-dev-1 whilst this should be harmless change, I've done it on a separate branch for now https://github.com/MaikuB/flutter_local_notifications/tree/10.0.0_launchIntent. You should be able to see the commit with the differences. I suspect at some point the launchIntent read in different from what gets read to actually get the launch details. If so, then this is something that should've been noticed sooner but I suppose your crash reports make it more noticeable. Let me know how it goes