Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for notification actions #338

Closed
wants to merge 1 commit into from

Conversation

noinskit
Copy link
Contributor

@noinskit noinskit commented Oct 2, 2019

Co-authored-by: Jakub Trzebiatowski [email protected]

@MaikuB
Copy link
Owner

MaikuB commented Oct 2, 2019

Won't get to look at this properly yet but wanted to bring a couple of this to your attention

  • Tests are failing so if you can take a look to see what needs to be fixed then that would be great
  • Looks like merge conflicts weren't resolved properly as this PR removes the changes I did recently in 0.8.4 release #337. These changes will need to be restored

@noinskit noinskit force-pushed the actions branch 3 times, most recently from c73f21b to 260a48e Compare October 3, 2019 10:14
@noinskit
Copy link
Contributor Author

noinskit commented Oct 3, 2019

Thanks. Both should be fixed now.

@TannerYoung
Copy link

@MaikuB I've read a few of the issue threads and understand your stance on waiting for generic support for actions. I haven't been able to thoroughly read through the code on this commit properly, but is this something that has the potential for being merged in to main, or should I (and others like me) fork this branch as a best alternative for the time being?

@MaikuB
Copy link
Owner

MaikuB commented Oct 4, 2019

@TannerYoung it may be potentially merged in but needs to be looked at in more detail so I would suggest you fork and use it.

It's been a while so unless I've forgotten, the only thing that was of more concern to me was more around being able to make sure the logic tied to the action buttons can be run even if the app isn't running. Based on the code I've seen so far, this is make use of the APIs for headless execution so should be fine on that front.

Will need to test it out though and see what scenarios might be missing. It would be great if you could assist in testing out the changes and provide feedback. I've had other things that required attention lately and there's a long weekend coming up (note: this may gave me some time to look at this but in the middle of something else atm that I'd like to finish if possible). Your patience and help would be greatly appreciated

@noinskit
Copy link
Contributor Author

noinskit commented Oct 4, 2019

"Being able to make sure the logic tied to the action buttons can be run even if the app isn't running" <-- yes, this is definitely the hard part! As you can probably already infer, the approach I took is that client code can react immediately to actions if it the app is running (can be in the background); but if it's not, they get queued up until next start. This is good enough for my use case (imagine the action being something like "mark this thing as done").

I don't know of any Flutter interface that would allow the client code to execute arbitrary logic in response to actions even when the app is down, without significantly complicating the interface.

@MaikuB
Copy link
Owner

MaikuB commented Oct 4, 2019

As you can probably already infer...
You are giving me too much credit :)
Been in a workshop for past two days so have only managed to gloss over it. Mostly responding to issues here and there when I can .

I had a suspicion because I saw that it didn't because headless execution generally only works with top-level functions but then I saw that there was code to makes use of the related APIs. Having taken a look at that now (e.g. in the receiver), looks like those APIs are used to make sure the actions are queued up so they're executed the next time initialize is called as you say. Is my understanding of that and the fact that the PR won't solve the problem of executing logic whilst the isn't running correct?

@noinskit
Copy link
Contributor Author

noinskit commented Oct 4, 2019

Yes, all correct.
This doesn't allow for executing client-defined logic in response to action when the application is not running at all.

I think it's a good tradeoff because:

  1. it covers lots of use cases (maybe I'm biased because it covers mine?),
  2. it allows for a much simpler API (because asking the plugin client to e.g. supply a global function that can bootstrap all its dependencies and process actions is a lot!)

@TannerYoung
Copy link

Found a minor regression:

AndroidNotificationDetails now requires that the 'icon' field be set for due to FlutterLocalNotificationsPlugin.java:159 where it now calls getDrawableResourceId(context, notificationDetails.icon).

It should instead check if there is an icon field, and if not use the defaultIcon.

@noinskit
Copy link
Contributor Author

noinskit commented Oct 6, 2019

@TannerYoung thanks for spotting! Should be fixed now, please try again. (FWIW arguably it wasn't a regression, it only triggered if you used actions.)

@TannerYoung
Copy link

Alright, I think I'm a bit lost as to what's supposed to be happening now. I've set up a onSelectNotificationAction handler to just print the action selected. After sending a local notification (before interacting with it in the UI) I get the log message Notification action data pieces to process: [], which implies that the onSelectAction in notification_action_queue is being triggered when a notification is sent. Due to this, the notification doesn't trigger my function when clicking on the actions via the UI.

It's possible that this is because I am running the plugin from a separate isolate (a non ui isolate). Thoughts on how to diagnose / fix this?

@MaikuB
Copy link
Owner

MaikuB commented Oct 7, 2019

@noinskit thanks for clarifying. In that case I'm going to wait for a generic solution that allows for logic to be executed whilst the app isn't running. Whilst what's in the PR may have some use cases, I don't think it covers enough to merge it in. When headless execution gets refined more that a generic solution can be worked on, it potentially requires coming with a way to migrate code (particularly for scheduled notifications) that makes use of the changes in this PR across.

@noinskit
Copy link
Contributor Author

noinskit commented Oct 8, 2019

@TannerYoung this message (Notification action data pieces to process: []) doesn't say much yet, because it should always be printed on plugin initialization in the current state. I'm happy that the notification has been rendered with actions, though.
I can't think of any reason why running the plugin from a background isolate would change anything, it should work.
What OS are you testing this on?
If it's Android, have you added <receiver android:name="com.dexterous.flutterlocalnotifications.NotificationActionReceiver" /> to the manifest?

@noinskit
Copy link
Contributor Author

noinskit commented Oct 8, 2019

@MaikuB I understand, this code can live in a fork in the meantime. I'm a bit sad that this won't be supported in the main plugin for what seems to be a long time, though. Is there actually any commitment at all from the flutter team to implement what you need (ideally with an ETA)?

@MaikuB
Copy link
Owner

MaikuB commented Oct 8, 2019

There are issues on the main repo around this that I have raised. Cant say much around the commitment side though there have been replies on the issues. The community should vote if they feel it's important as the team does look those. The meta-bug collecting some of this is tracked in flutter/flutter#32164

@TannerYoung
Copy link

Thanks for the pointer to android manifest! I've got two more bugs:

  1. With multiple (tested with 2) actions, though it properly displays the action title in the message, it only sends the id of whichever action is second regardless of which action is clicked.

  2. This is more niche to my use case - I'm running with firebase_messaging. If I launch a notification from the background, all works fine and I can continue to send notification after notification. However, once I click on an action on the notification (even a simple one that just logs the action id), further notifications refuse to show with the following error: FlutterNativeView: FlutterView.send called on a detached view, channel=plugins.flutter.io/firebase_messaging_background. Is it possible that your action changes detach the flutter view? To be honest I'm a bit out of my expertise here and not sure what the problem would be, but I know this only happens when interacting with an action. Thoughts on what this could be?

@MaikuB
Copy link
Owner

MaikuB commented Oct 8, 2019

Closing this PR since it won't be merged in. This may affect seeing updates being added so you may need to monitor the fork

@MaikuB MaikuB closed this Oct 8, 2019
@noinskit
Copy link
Contributor Author

noinskit commented Oct 8, 2019

@TannerYoung (1) - sorry for that. It was a simple bug and should now be fixed. Please try again. (You need to get it from https://github.com/noinskit/flutter_local_notifications/tree/actions as this PR doesn't appear to be getting updates anymore.)

As for (2) - I don't know what this could be right away. I'll dig into that.

@noinskit
Copy link
Contributor Author

noinskit commented Oct 9, 2019

@TannerYoung about (2)... am I correct to assume that it's also about Android?

I've got one suspicion - it might be about the Firebase plugin not liking to be initialized multiple times.
In Application.java, by now you probably have something like (the following is copied from this plugin's "example"):

    @Override
    public void onCreate() {
        super.onCreate();
        FlutterLocalNotificationsPlugin.setPluginRegistrant(this);
    }

    @Override
    public void registerWith(PluginRegistry registry) {
        GeneratedPluginRegistrant.registerWith(registry);
    }

Could you please try changing the latter to be more specific:

    @Override
    public void registerWith(PluginRegistry registry) {
       SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
    }

?

@TannerYoung
Copy link

Yeah, working with Android, and thanks for your help I'm rather new to the space.

public class MyApplication extends FlutterApplication implements PluginRegistrantCallback {

  @Override
  public void onCreate() {
    super.onCreate();
    FlutterFirebaseMessagingService.setPluginRegistrant(this);
    FlutterLocalNotificationsPlugin.setPluginRegistrant(this);
  }

  @Override
  protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
  }

  @Override
  public void registerWith(PluginRegistry pluginRegistry) {
    SharedPreferencesPlugin.registerWith(pluginRegistry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
  }

With that above I get the slightly worrying log error:

2019-10-10 09:44:30.069 6475-6536/com.epigram.network E/flutter: [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: MissingPluginException(No implementation found for method FcmDartService#initialized on channel plugins.flutter.io/firebase_messaging_background)
    #0      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:314:7)
    <asynchronous suspension>
    #1      _fcmSetupBackgroundChannel (package:firebase_messaging/firebase_messaging.dart:50:21)
    #2      _AsyncAwaitCompleter.start (dart:async-patch/async_patch.dart:43:6)
    #3      _fcmSetupBackgroundChannel (package:firebase_messaging/firebase_messaging.dart:23:32)
    #4      _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:229:25)
    #5      _rootRun (dart:async/zone.dart:1124:13)
    #6      _CustomZone.run (dart:async/zone.dart:1021:19)
    #7      _runZoned (dart:async/zone.dart:1516:10)
    #8      runZoned (dart:async/zone.dart:1500:12)
    #9      _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:221:5)
    #10     _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:305:19)
    #11     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12)

But the multiple notifications work! Thanks for your help :D

@TannerYoung
Copy link

I'm not sure I fully understand the above solution - if you have the time I'd very much appreciate an explanation of why that worked.

@noinskit
Copy link
Contributor Author

@TannerYoung I see, so the firebase plugin is also registering dependencies. Could you please try something similar to (warning: not tested):

public class MyApplication extends FlutterApplication {
    @Override
    public void onCreate() {
        super.onCreate();
        FlutterFirebaseMessagingService.setPluginRegistrant(new PluginRegistrantCallback() {
            @Override
            public void registerWith(PluginRegistry registry) {
                GeneratedPluginRegistrant.registerWith(registry);
            }
        });
        FlutterLocalNotificationsPlugin.setPluginRegistrant(new PluginRegistrantCallback() {
            @Override
            public void registerWith(PluginRegistry registry) {
                SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
            }
        });

  @Override
  protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
  }
}

I hope it makes sense - this way we wouldn't change the callback passed to FlutterFirebaseMessagingService, but we'd narrow down what FlutterLocalNotificationsPlugin does for plugin registration.

@noinskit
Copy link
Contributor Author

Oh, maybe I wasn't clear - the changes in your MyApplication.java that I'm asking for is following a hypothesis about this problem you described about how clicking on actions is breaking firebase_messaging with error about "detached view". It's still only a hypothesis, it might be wrong.

The problem with multiple notifications was totally unrelated and it was a simple fix in the (forked) plugin code.

@TannerYoung
Copy link

Great - got it working and thanks for the explanation. If I encounter more issues I'll raise them on the fork repo.

Thanks a ton for your work here.

@noinskit
Copy link
Contributor Author

Great, I'm happy that my guess on firebase was correct! Feel free to use the fork and report issues, and let's see how the situation evolves from there.

@0xecute
Copy link

0xecute commented Oct 29, 2019

Hi. I am trying to use your plugin.
I did the following:

flutter_local_notifications:
    git:
      url: https://github.com/noinskit/flutter_local_notifications
      ref: actions

Then in AndroidManifest.xml:
<receiver android:name="com.dexterous.flutterlocalnotifications.NotificationActionReceiver" />

The notification is showing correctly, but when I click on an action, it throw me a fatal error:

I/NotifActionReceiver(14983): NotificationActionReceiver received: {"autoCancel":true,"body":"notification with actions body","category":{"actions":[{"identifier":"ACTION_YAY","title":"Yay"},{"identifier":"ACTION_NAY","title":"Nay"}],"identifier":"MY_NOTIFICATION_CATEGORY"},"categoryIdentifier":"MY_NOTIFICATION_CATEGORY","channelAction":"CreateIfNotExists","channelDescription":"notification with actions channel description","channelId":"notification with actions channel id","channelName":"notification with actions channel name","channelShowBadge":true,"enableLights":false,"enableVibration":true,"groupAlertBehavior":0,"id":0,"importance":3,"indeterminate":false,"maxProgress":0,"payload":"item x","playSound":true,"priority":0,"progress":0,"showProgress":false,"style":"Default","styleInformation":{"type":"DefaultStyleInformation","htmlFormatBody":false,"htmlFormatTitle":false},"title":"notification with actions title"}
D/AndroidRuntime(14983): Shutting down VM
E/AndroidRuntime(14983): FATAL EXCEPTION: main
E/AndroidRuntime(14983): Process: ch.playDogs, PID: 14983
E/AndroidRuntime(14983): java.lang.RuntimeException: Unable to start receiver com.dexterous.flutterlocalnotifications.NotificationActionReceiver: java.lang.NullPointerException: Attempt to invoke interface method 'void io.flutter.plugin.common.PluginRegistry$PluginRegistrantCallback.registerWith(io.flutter.plugin.common.PluginRegistry)' on a null object reference
E/AndroidRuntime(14983): 	at android.app.ActivityThread.handleReceiver(ActivityThread.java:3399)
E/AndroidRuntime(14983): 	at android.app.ActivityThread.-wrap18(Unknown Source:0)
E/AndroidRuntime(14983): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1780)
E/AndroidRuntime(14983): 	at android.os.Handler.dispatchMessage(Handler.java:105)
E/AndroidRuntime(14983): 	at android.os.Looper.loop(Looper.java:164)
E/AndroidRuntime(14983): 	at android.app.ActivityThread.main(ActivityThread.java:6944)
E/AndroidRuntime(14983): 	at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime(14983): 	at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
E/AndroidRuntime(14983): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
E/AndroidRuntime(14983): Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'void io.flutter.plugin.common.PluginRegistry$PluginRegistrantCallback.registerWith(io.flutter.plugin.common.PluginRegistry)' on a null object reference
E/AndroidRuntime(14983): 	at com.dexterous.flutterlocalnotifications.NotificationActionReceiver.onReceive(NotificationActionReceiver.java:68)
E/AndroidRuntime(14983): 	at android.app.ActivityThread.handleReceiver(ActivityThread.java:3392)
E/AndroidRuntime(14983): 	... 8 more

Can anyone help me with that?
Thank you!

@TannerYoung
Copy link

Probably open a new issue on the fork repo https://github.com/noinskit/flutter_local_notifications.git

But what mine looks like:

pubspect.yaml

flutter_local_notifications:
    git:
      url: https://github.com/noinskit/flutter_local_notifications.git
      ref: actions

AndroidManifest.xml:

<receiver android:name="com.dexterous.flutterlocalnotifications.NotificationActionReceiver" />

MyApplication.java

public class MyApplication extends FlutterApplication implements PluginRegistrantCallback {

  @Override
  public void onCreate() {
    super.onCreate();
    // Other calls to `.setPluginRegistrant(this)`
    FlutterLocalNotificationsPlugin.setPluginRegistrant(new PluginRegistrantCallback() {
      @Override
      public void registerWith(PluginRegistry registry) {
      SharedPreferencesPlugin.registerWith(registry.registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
      }
    });
  }

  @Override
  public void registerWith(PluginRegistry pluginRegistry) {
    GeneratedPluginRegistrant.registerWith(pluginRegistry);
  }
}

@0xecute
Copy link

0xecute commented Oct 29, 2019

Thanks. I didn't have the MyApplication.java part.
Now the application is working I think :)
Thank you again for this great plugin!

@noinskit
Copy link
Contributor Author

Thanks @TannerYoung for responding faster than I saw this! :) In the future, you're welcome to file issues directly under this fork.

@xjerryg
Copy link

xjerryg commented Dec 11, 2019

Hi. Is there a way to have one action button as "background action" (dismiss the notification and do some background action ...if I understand it right, after the next start of the app) and the second button to open the app on specific screen? Thanks.

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants