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:

mit-mit commented 6 years ago

Hi @MaikuB do you have an example use case where of using a custom notification action on iOS?

MaikuB commented 6 years ago

@mit-mit

I believe the support for this would be covered in https://github.com/flutter/flutter/issues/3671 and help enable more fully featured apps. That thread covers other use cases that would require headless-Dart execution.

It'd also be good if the team could provide more documentation on how headless execution works on Android. I tried to look at how to do this to implement custom actions on Android and to handle another request (#21) by mimicking the changes I saw in https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager but wasn't successful. Haven't checked in that code can do so to another branch if your team has capacity to provide input

mitchhymel commented 6 years ago

@MaikuB I worked a bit on my own notification plugin and while it's not fully featured as yours, I did get custom actions working with headless Dart in Android (at least from my testing of using my app daily for the past month, it seems to be working).

You need to create an Android service, that will host your shared method channel and support handling the intent in the background. Make sure to set up the shared channel when the plugin is registered. Then when you are creating your notification, if it is meant to run in the background, the intent must be created with your Service class. Then in your Service's onHandleIntent, you want to get the shared channel and invoke your callback to dart. And then here's where I handle the call from the Android service in Dart.

Hopefully that helps. Great plugin by the way. Your plugin has more features and you seem to be more active in development than I am, so I think I'll add a reference to your plugin in my readme, in case people want the features your plugin offers.

MaikuB commented 6 years ago

@mitchhymel thanks for the tips and appreciation :)

However, does that cover when the application has been terminated as well? That's the main scenario i'm looking at it that I have concerns about? If you look at what's in the android_alarm_manager plugin, you can see what they try to do is resolve an instance of the Flutter application etc to find the callback to invoke. That's what I believe to be crucial in handling that scenario and haven't seen in your code. I already handle intents via the plugin as it implements the NewIntentListener

mitchhymel commented 6 years ago

I'm not sure if it handles when the application has been terminated. I haven't tested it yet with my code. From your description, and looking at the AlarmService code, I'm thinking my code would not handle the case where the app is terminated. So maybe my code is not a helpful example.

MaikuB commented 6 years ago

Fair enough. i've managed to at least handle it so that when app is terminated when comes to tapping the notification and then triggering a callback after launch. Anyway, appreciate you trying to provide help :)

MaikuB commented 6 years ago

@mit-mit i've figured out what i was missing on the Android side of things now but be good to see if the team is able to find a solution for iOS :)

MaikuB commented 6 years ago

@mit-mit i mentioned this on the main Flutter repo but don't know if anyone from your team has seen it given there hasn't been a response. I've taken a look at how Dart code is executed from Android and from what I can gather, it doesn't seem like the APIs that have been exposed allow for passing back arguments from the Andoid side back to the Dart function that will be invoked. This prevents being able to implement a lot of use cases e.g. knowing which alarm to snooze once the callback has been received on the Dart side

mit-mit commented 6 years ago

@MaikuB let me ask around a bit, but may take a while as the whole team is really busy getting ready for I/O next week.

MaikuB commented 6 years ago

Awesome, i understand so thanks for that. Looking forward to see what's on next week :)

MaikuB commented 6 years ago

@mit-mit since I/O is done now, just wanted to see if were you able to find more information about this. Also. I know Hixie said iOS headless execution is being looked into but whilst doing that, it'd be good if the team looks at if it's possible to do that and pass payload back from the iOS side as well

pinkfish commented 6 years ago

Doesn't the firebase messaging plugin handle this? I am pretty sure it does processing without pulling the app to the foreground...

MaikuB commented 6 years ago

Nope, take a look at the README file for it on how it handles when the app is terminated and you'll see this

Notification is delivered to system tray. When the user clicks on it to open app onLaunch fires if click_action: FLUTTER_NOTIFICATION_CLICK is set (see below).

How I handle when the user taps on the notification at the moment is pretty much identical except it's consolidated to a single event handler

pinkfish commented 6 years ago

I followed the idea above from @mitchhymel and it seems to work. It is opening up the app and calling the method on it to say the button was clicked on. How do you verify this scenario? I am swiping the app away in the activity list. I suspect this will make the startup tricky though since the ordering will likely be an issue when the activity starts. Since Mitch is using getActivitry on the pending intent it is opening up the app. Got it working correctly on ios too with categories.

pinkfish commented 6 years ago

It won't do background processing in this case though by the looks :) Which is likely what you are worried about? It will only work if it forces the app into the foreground and does something. Which is a bit disappointing.

MaikuB commented 6 years ago

What you're doing is actually not that much different to how I handle when the app is terminated and the user taps on the notification, i.e. launch the app and trigger the callback that allows apps to handle the action that occurred.

Yes, I'm concerned with running code in the background even when the app is terminated and as @mitchhymel mentioned, I don't believe his code handled that either. This would help enable scenarios like being able to snooze an alarm and skipping music tracks when the app has been terminated. The mechanisms to achieve this are different (one of which is to have the intent trigger a service) and appear to also handled the case when the app is currently running. However, as I mentioned earlier, the APIs that are available don't allow passing parameters back. I'd much prefer to only add shipping this feature when it's possible to handle all the scenarios (i.e. when is terminated or running). Otherwise, I'd end up with code that needs to be changed later or devs seeing issues raised even when a limitation has been documentation but they didn't bother reading about it, which i'm also guilty of :)

Anyway, hope you understand where I'm coming from

pinkfish commented 6 years ago

I can see that. I just did a slightly different change that lets you open a url on an action instead. This lets you do things like driving directions from the buttons too. All of this sorts out what I need to do with buttons... Although the fact that firebase messages get dropped on the floor in the background is a bit of a bastard :)

On 21 May 2018 at 17:58, Michael Bui notifications@github.com wrote:

What you're doing is actually not that much different to how I handle when the app is terminated and the user taps on the notification, i.e. launch the app and trigger the callback that allows apps to handle the action that occurred.

Yes, I'm concerned with running code in the background even when the app is terminated. This would help enable scenarios like being able to snooze an alarm and skipping music tracks when the app has been terminated. The mechanisms to achieve this are different (one of which is to have the intent trigger a service) and appear to also handled the case when the app is currently running. However, as I mentioned earlier, the APIs that are available don't allow passing parameters back. I'd much prefer to only add shipping this feature when it's possible to handle all the scenarios (i.e. when is terminated or running). Otherwise, I'd end up with code that needs to be changed later or devs seeing issues raised even when a limitation has been documentation but they didn't bother reading about it, which i'm also guilty of :)

Anyway, hope you understand where I'm coming from

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/MaikuB/flutter_local_notifications/issues/17#issuecomment-390829519, or mute the thread https://github.com/notifications/unsubscribe-auth/ACbmMQYZU2dHCAV1s2t3D-CC52XH4Ldlks5t02KagaJpZM4TeOyk .

honga commented 6 years ago

Great example @MaikuB -a social media app may notify users when someone has replied their post, in which case actions may be presented to allow the user to either reply or like the reply Looking forward to be able to do something like a direct reply/voice recording as well

MaikuB commented 6 years ago

@pinkfish too right mate :)

s-bauer commented 6 years ago

Any Update on this? There are quite a lot of use-cases that require custom actions!

MaikuB commented 6 years ago

I'm well aware of that. You should follow the issues linked in the original post. There are open PRs related to this that need to go in first to enable this to work better, particularly when the app isn't running. If it's that urgent and you don't care about scenarios like that (via headless Dart execution) then I'd suggest you fork to roll a custom build.

Edit: I can see you've already commented on one of the PRs

figelwump commented 6 years ago

Hi, with this issue now resolved: https://github.com/flutter/flutter/issues/3671, does that unblock the work for this issue? If so, what are you currently thinking for ETA?

This functionality is critical for a project I'm currently working on, I appreciate your work on this!

MaikuB commented 6 years ago

It does unblock from looking at it but it's not rolled over to beta channel and that's where a lot of devs will likely to be on. So if it were done it would be a blocker for release until the changes rolled into beta. Last i checked dev was on 0.5.8 (I believe the changes have rolled into there) and there are reports of hot reloading or restarting being broken (think I ran into an issue too) and devs rolling back to the previous dev build (0.5.7) so there are concerns around stability there. From what I read the reason why beta hasn't been updated for a long time is so the Flutter team can do more rigorous testing as well.

I'm in the midst of the working on an app (along with some other work) so hard to give an ETA at the moment though I'll likely get to test the headless execution in the next few days e.g. see if #21 would work on the Dev channel.

If you or someone from the community would be able to assist then that would be great. @pinkfish submitted a PR a while back but that was before headless execution work was done and would be a good starting point

figelwump commented 6 years ago

Thanks for the update! I'm curious what you learn from working on #21 (I'll subscribe to that issue as well)

I'm not able to assist at the moment, but I could possibly help in a few weeks. If you or someone else is able to get started in the meanwhile, that would help give this momentum! I'm happy to join in as soon as I can.

MaikuB commented 6 years ago

21 requires implementing all that's required for headless Dart execution to work and the functionality is less complex than doing custom actions. Makes it more easier to test headless Dart execution and setup the foundations for this issue. Not sure how much I'll be able to piece it together but If you followed the issue on the Flutter repo, you'll see that documentation and examples are work in progress. The latter I believe is being done here so will be using that repo and this one as a reference for the iOS side.

pinkfish commented 6 years ago

Is this even possible on iOS? I didn't think you got any callbacks on notifications there.

MaikuB commented 6 years ago

Are you referring to #21 around a callback when a notification is to be shown?

pinkfish commented 6 years ago

Right, yes. Doing a callback on a scheduled notification. Can you run code then? You cannot on remote notifications right?

MaikuB commented 6 years ago

There is at least this callback, which isn't specific to a scheduled notification when the app is in the foreground

https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter

I should clarify that I wasn't looking at #21 specifically to scheduled notifications but for (local) notifications in general. Headless Dart execution on iOS via the callback I linked isn't necessary but would help to see if it will still works

davystrong commented 5 years ago

Any update on this issue? I'd like to use this plugin for an app but the quick actions are essential. I saw a reference to it already being possible on Android, which is the platform I am primarily developing for.

MaikuB commented 5 years ago

@davystrong none at the moment. I previously tried to add headless execution of Dart code (this is relevant so code can be run even if the app is terminated) but ran into some issues that appeared to be related to isolates. I had raised an issue in this on the Flutter repo and believe they were going to look at how to make this easier for plugin developers. Am not sure what the status is on this

MatMaul commented 5 years ago

@MaikuB I just finished a FCM push plugin that works with a terminated app. With the good headless initialization code it's actually quite easy. I did the work only for Android since I am clueless regarding iOS. My understanding is that it should not be that difficult, and probably simpler than the Android part. I am planning to work on this item now since I am missing quite a bit the actions now that I have push for a messaging app :)

MaikuB commented 5 years ago

i suspect you'll run into issues as headless execution relies on running on a background isolate but we also want actions to be able to run code on the main isolate so that it can update the UI when the app is running. I ran into similar problems (on Android) whilst looking at the geofencing sample and have already reported an issue on the main Flutter repo around this

MatMaul commented 5 years ago

For now I don't need to live update the UI to be frank so I am happy with their isolate impl. It allows me to read the DB of my app, fetch some messages from the server and display them in the notif. But I am interested in the problem, of the UI update, can you link me the bug ? Thanks !

MaikuB commented 5 years ago

If you're not concerned with that then I'd suggest that you implement it in your own fork. I'd prefer the UI issue gets solved as well to avoid the scenario where the headless implementation may need to be rewritten when the issue gets resolved. The issue I'm referring to is https://github.com/flutter/flutter/issues/23904

davystrong commented 5 years ago

@MatMaul Are you planning to implement this? I believe this would also solve my problem.

vikramaggarwal13 commented 5 years ago

This plugin is very supportable in all type of local nofifications but please introduce one more thing where we can pass widget also to notification bar . Where we can show our customized look on nofication area

MaikuB commented 5 years ago

@vikramaggarwal13 Based on what I know, I don't think that is achievable. Doing custom layouts for notifications on each platform, requires using the way UI is done on that platform e.g. layout files for Android https://developer.android.com/training/notify-user/custom-notification

vikramaggarwal13 commented 5 years ago

hmm Okay , But is there any way i want to introduce pause play button on notification area . I want user to play or pause music directly from notification area.

MaikuB commented 5 years ago

not at this stage, the reasons why have been discussed above already

vikramaggarwal13 commented 5 years ago

Ok Alright

MaikuB commented 5 years ago

To clarify i'm referring to via this plugin. an alternative that you look at writing code that uses platform channels yourself. This may likely require you to invoke the media playback functionality on the Android/iOS side as well

martinory commented 5 years ago

Any updates? Not experienced with Android but looked into the code and wonder if it would be possible to do similar to https://github.com/transistorsoft/flutter_background_fetch (which can call Flutter when the application is terminated) and change the HeadlessBroadcastReceiver to listen to respond to notification actions instead?

martinory commented 5 years ago

I made it work by changing android_alarm_manager (fork: https://github.com/flutter/plugins/pull/2013) and sending pending intents to its broadcast receiver. This then worked when the app was terminated as well. To create actions, I changed the notification details to contain the text for the action button, method to be invoked and the args as a json string.

            Intent actionIntent1 = new Intent(NOTIFICATION_ACTION);
            actionIntent1.putExtra("notificationId", notificationDetails.id);
            actionIntent1.putExtra("method", notificationDetails.firstActionMethod);
            actionIntent1.putExtra("arguments", notificationDetails.firstActionArgs);
            PendingIntent actionPendingIntent1 =
                    PendingIntent.getBroadcast(context.getApplicationContext(), notificationDetails.id + 1, actionIntent1, PendingIntent.FLAG_UPDATE_CURRENT);
            builder.addAction(0, notificationDetails.firstActionText, actionPendingIntent1);
robindijkhof commented 5 years ago

@MaikuB I read multiple times about "termination". What does this actually mean? Are we talking about "swiping up" from recent apps? Or are we talking about stopping the app from the app-info page in settings?

Also, maybe you can update the OP, as I think you'll get the question: "any update on this" many more times.

MaikuB commented 5 years ago

@robindijkhof: it means when the app isn't running or has been killed/shutdown through whatever means (e.g. swiping up depending on the OS).

Regarding updating the OP, there's actually a blocked label on this issue already. I can update it but not sure if it'll help much at the end. Thanks for the suggestion though.

dereklakin commented 5 years ago

@martinory Any chance you could make a PR to add this feature here?

MaikuB commented 5 years ago

@dereklakin my understanding of what @martinory has done is dependent on the android alarm manager plugin (would need more details on why there's a dependency on that) so likely only works in specific scenarios. There are other scenarios this should handle where I found it's failing (see previous replies) hence why this is still blocked (note: there are labels on this issue)

martinory commented 5 years ago

@MaikuB I did use android_alarm_manager due to that they've already had figured out headless execution of dart code when the app is terminated and that it's well maintained. I did modify the package so it's not the official version of the plugin. In the plugin (android_alarm_manager), there is a todo in the java code that says that they might change the plugin to something similar to the changes I made but I don't know. I did do this just to investigate how it could be solved, there are probably many other ways to do it. Have no idea with iOS because I'm not into that.

I would have a function in my own code similar to:

  _channel.setMethodCallHandler((call) {
    final String method = call.method;
    final String args = call.arguments[0];

    switch (method) {
      case "background":
        background();
        break;
      case "triggerAction":
        triggerAction(args);
        break;
      default:
        print('[backgroundCallbackDispatcher] Didn\'t invoke any method ($method)');
        break;
    }
  });

And then in the notification details (i also created a copyWith function):

notificationDetails.android.copyWith(
              firstActionText: 'TEXT FOR AN ACTION',
              firstActionMethod: 'triggerAction',
              firstActionArgs: actionPayload.serialize(),
noinskit commented 4 years ago

If anybody's interested, we implemented support for notification actions in a fork (pull request: https://github.com/MaikuB/flutter_local_notifications/pull/338). As @MaikuB wrote, it's not straightforward due to complexity of headless execution, so it's one of multiple possible approaches.