MaikuB / flutter_local_notifications

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

Asking for notification permissions in Android causes lifecycle state change #2363

Closed darajava closed 3 months ago

darajava commented 4 months ago

Describe the bug

To Reproduce 1) Check for notification permission on Android, where permission was already rejected.

final AndroidFlutterLocalNotificationsPlugin? androidImplementation =
    _localNotificationsPlugin.resolvePlatformSpecificImplementation<
        AndroidFlutterLocalNotificationsPlugin>();

await androidImplementation?.requestNotificationsPermission();
  1. Observe that requestNotificationsPermission on Android returns false, a dialog does not show, but a state change is triggered.
  2. Observe trigger in following code:

    void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    
    if (state == AppLifecycleState.resumed) {
    print("TRIGGERED");
    }
    }

Expected behavior It should not trigger a state change.

Sample code to reproduce the problem As above

bleonard252 commented 4 months ago

I don't think this is a bug. It looks like it triggers another activity, thus causing the state to change, and then it returns to the Flutter activity, causing the state to change again. It all just happens really fast.

Potentially this could be avoided by checking the permission status before attempting to trigger the system permission dialog.

darajava commented 4 months ago

But why should it trigger another activity when nothing will show up to the user? A modal does not appear at all, and therefore it's not logical to expect a state change. (It's not logical to expect one even if a modal did show up, since it's an overlay. Apple does it this way). This caused a really bad and subtle bug for me, and I think there is no harm checking the permission in that function.

MaikuB commented 4 months ago

Before I go into this further, note that requestNotificationsPermission() isn't a check but request. This is in response to what was mentioned in step 1 of the original message. To check, the method to use would be areNotificationsEnabled().

To summarise though, this isn't a bug with the plugin. Lifecycle changes happening are outside the control of the plugin. What the plugin does is calls native APIs of the platform and what happens afterwards is due to how the native platform behaves. Were you to write the Android code yourself via Java or Kotlin, you'll experience the same behaviour with activities being resumed. A comparison with iOS isn't apt either as they're different platforms with different behaviours. In saying that, I'm not sure what your point about Apple was when I've seen the resume lifecycle event fire after a selection is made through the iOS permission prompt.

This caused a really bad and subtle bug for me, and I think there is no harm checking the permission in that function.

When a user has denied permissions, it is actually possible for the dialog to be shown again that implementing this logic would prevent this from happening. This is covered in the official Android documentation and is where developers need to understand how the target platform behaves. This is a prerequisite before looking at this plugin even so that developers what can/can't be done for their app. Back the permissions prompt though, this can be seen this up to two times. That is, they can deny it once, be shown a prompt again and that's it. Implementing a check to avoid showing a prompt after it's been denied would prevent the ability to show the prompt again and go against the official best practices/recommendations. This excludes the check if permissions rationale can be optionally be shown (note: I believe the permission_handler provides a way to check this if you need it

This also means the steps mentioned in the original post aren't necessarily correct. Given you hadn't mentioned the dialog in the original post, I suspect what you're actually referring to when the user has denied the permissions twice and tried to make a request again when it should be known at this point that it would not show a prompt at all. To my point earlier, the resume lifecycle event happening at this point is also consistent with what happens if you invoked the same logic via Java/Kotlin yourself. If you are indeed referring to the scenario where user has denied twice then that comes down to application-specific logic to handle this scenario much like how Android provides a workflow that is for applications to follow in the link shared earlier. In other words, similar to what @bleonard252 said, it sounds like your app is missing logic to avoid making requests again. The other thing to consider is if you have logic on the resume lifecycle event that is better placed elsewhere e.g. initState() so it's only fired once. The last point may not be applicable but thought I'd mentioned