Skip to content

[Bug]: Callbacks like onGrantedCallback does not work as expected. #1414

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

Open
3 of 5 tasks
ebsangam opened this issue Dec 9, 2024 · 11 comments
Open
3 of 5 tasks

[Bug]: Callbacks like onGrantedCallback does not work as expected. #1414

ebsangam opened this issue Dec 9, 2024 · 11 comments
Assignees

Comments

@ebsangam
Copy link

ebsangam commented Dec 9, 2024

Please check the following before submitting a new issue.

Please select affected platform(s)

  • Android
  • iOS
  • Windows

Steps to reproduce

  1. Use a callback Permission.contacts.onGrantedCallback
  2. Grant permission for camera.
  3. Permission.contacts.onGrantedCallback will be invoked.

Expected results

When using Permission.contacts.onGrantedCallback we expect this callback to invoke only if we grant permission for contacts.

Actual results

No mater what permission you add callback it will always gets invoked for every permission.

Code sample

Code sample
Permission.contacts.onGrantedCallback(
  () {
    print('Contacts permission granted.');
  },
);

Screenshots or video

Screenshots or video demonstration

[Upload media here]

Version

11.0.1

Flutter Doctor output

Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.24.3, on macOS 14.6.1 23G93 darwin-arm64, locale en-NP)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.2)
[✓] VS Code (version 1.95.3)
[✓] Connected device (4 available)
[✓] Network resources

• No issues found!
@TimHoogstrate
Copy link
Contributor

Dear @ebsangam,

Can you elaborate a bit on the subject? Are you testing this in iOS or Android? And what version of OS?

Kind regards,

@TimHoogstrate TimHoogstrate self-assigned this Dec 11, 2024
@TimHoogstrate TimHoogstrate added the status: needs more info We need more information before we can continue work on this issue. label Dec 11, 2024
@ebsangam
Copy link
Author

I was testing on Android emulator SDK 34. I wanted to listen to contacts permission status change (on granted to be specific). So I used Permission.contacts.onGrantedCallback. But the callback gets invoked when I grant camera permission or microphone permission. Basically Permission.contacts.onGrantedCallback is not only listening to contacts permission updated but for every permission updates. Thanks.

@github-actions github-actions bot removed the status: needs more info We need more information before we can continue work on this issue. label Dec 11, 2024
@12people
Copy link

@TimHoogstrate

Running into the same issue. (Only tried building on Android so far, but I imagine all the platforms have this error.)

Here's a full flutter app code to replicate it:

import 'package:flutter/material.dart';

import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(const MainApp());
}

class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  PermissionStatus? locationState;
  PermissionStatus? notificationState;

  @override
  Widget build(BuildContext context) {
    const locationPermission = Permission.locationAlways;
    locationPermission.onDeniedCallback(() {
      setState(() => locationState = PermissionStatus.denied);
    });
    locationPermission.onGrantedCallback(() {
      setState(() => locationState = PermissionStatus.granted);
    });
    locationPermission.onLimitedCallback(() {
      setState(() => locationState = PermissionStatus.limited);
    });
    locationPermission.onPermanentlyDeniedCallback(() {
      setState(() => locationState = PermissionStatus.permanentlyDenied);
    });
    locationPermission.onProvisionalCallback(() {
      setState(() => locationState = PermissionStatus.provisional);
    });
    locationPermission.onRestrictedCallback(() {
      setState(() => locationState = PermissionStatus.restricted);
    });

    const notificationPermission = Permission.notification;
    notificationPermission.onDeniedCallback(() {
      setState(() => notificationState = PermissionStatus.denied);
    });
    notificationPermission.onGrantedCallback(() {
      setState(() => notificationState = PermissionStatus.granted);
    });
    notificationPermission.onLimitedCallback(() {
      setState(() => notificationState = PermissionStatus.limited);
    });
    notificationPermission.onPermanentlyDeniedCallback(() {
      setState(() => notificationState = PermissionStatus.permanentlyDenied);
    });
    notificationPermission.onProvisionalCallback(() {
      setState(() => notificationState = PermissionStatus.provisional);
    });
    notificationPermission.onRestrictedCallback(() {
      setState(() => notificationState = PermissionStatus.restricted);
    });

    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                for (final permissionEntry
                    in {
                      locationPermission: locationState,
                      notificationPermission: notificationState,
                    }.entries)
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text("${permissionEntry.key}: ${permissionEntry.value}"),
                      TextButton(
                        onPressed: () {
                          permissionEntry.key.request();
                        },
                        child: Text("REQUEST ${permissionEntry.key}"),
                      ),
                    ],
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Of course, you'll need to add the requisite permissions to Android manifest as well, and add permission_handler to pubspec.yaml as well:

<!-- Permissions options for the `access notification policy` group -->
    <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>

    <!-- Permissions options for the `notification` group -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

    <!-- Permissions options for the `location` group -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
  permission_handler: ^11.4.0

@12people
Copy link

Here's a recording of the odd behavior:

error-recording.mp4

(Same code as I posted above.)

@12people
Copy link

As for my Android version, it's Android 13 (build 6.A.031.7).

@TimHoogstrate
Copy link
Contributor

@12people,

Requesting the permission works fine (also in the example app), you can check it in the app settings after requesting and approving the permissions. However, your demo app is just presenting the results wrongly. Try to keep the registration of the callbacks outside of the build method.

@ebsangam I've verified that is works properly in the example app.

Kind regards,

@TimHoogstrate TimHoogstrate added the status: needs more info We need more information before we can continue work on this issue. label Mar 18, 2025
@ebsangam
Copy link
Author

ebsangam commented Mar 18, 2025

@12people,

Requesting the permission works fine (also in the example app), you can check it in the app settings after requesting and approving the permissions. However, your demo app is just presenting the results wrongly. Try to keep the registration of the callbacks outside of the build method.

@ebsangam I've verified that is works properly in the example app.

Kind regards,

I think you misunderstood the issue. Let's not focus on the example app instead my original issue I mentioned. It is the issue about callback that fires unnecessarily.

@github-actions github-actions bot removed the status: needs more info We need more information before we can continue work on this issue. label Mar 18, 2025
@TimHoogstrate
Copy link
Contributor

Dear @ebsangam,

Still I cannot reproduce this. Please provide me with a clear sample. Again, I tested this but I cannot reproduce this. The sample of @12people is just not working properly. If I change any of the permissions from Permission.notification to another (for example contacts I get the same results (only then with .contacts).

Kind regards,

@TimHoogstrate TimHoogstrate added the status: needs more info We need more information before we can continue work on this issue. label Mar 18, 2025
@ebsangam
Copy link
Author

I will provide you a minimal reproducible code when I am free.

@github-actions github-actions bot removed the status: needs more info We need more information before we can continue work on this issue. label Mar 18, 2025
@TimHoogstrate TimHoogstrate added the status: needs more info We need more information before we can continue work on this issue. label Mar 18, 2025
@12people
Copy link

@TimHoogstrate

You're right, setting callbacks in the build method isn't a good idea. (I guess I was too focused on making a single-file example. In my real-world usage, I tried it with a Riverpod provider and didn't want to burden the example with that complexity.)

However, if I set the callbacks once in the initState method, I get the same result:

import 'package:flutter/material.dart';

import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(const MainApp());
}

class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  static const _locationPermission = Permission.locationWhenInUse;
  static const _notificationPermission = Permission.notification;
  PermissionStatus? locationState;
  PermissionStatus? notificationState;

  @override
  void initState() {
    super.initState();
    _locationPermission.onDeniedCallback(() {
      setState(() => locationState = PermissionStatus.denied);
    });
    _locationPermission.onGrantedCallback(() {
      setState(() => locationState = PermissionStatus.granted);
    });
    _locationPermission.onLimitedCallback(() {
      setState(() => locationState = PermissionStatus.limited);
    });
    _locationPermission.onPermanentlyDeniedCallback(() {
      setState(() => locationState = PermissionStatus.permanentlyDenied);
    });
    _locationPermission.onProvisionalCallback(() {
      setState(() => locationState = PermissionStatus.provisional);
    });
    _locationPermission.onRestrictedCallback(() {
      setState(() => locationState = PermissionStatus.restricted);
    });

    _notificationPermission.onDeniedCallback(() {
      setState(() => notificationState = PermissionStatus.denied);
    });
    _notificationPermission.onGrantedCallback(() {
      setState(() => notificationState = PermissionStatus.granted);
    });
    _notificationPermission.onLimitedCallback(() {
      setState(() => notificationState = PermissionStatus.limited);
    });
    _notificationPermission.onPermanentlyDeniedCallback(() {
      setState(() => notificationState = PermissionStatus.permanentlyDenied);
    });
    _notificationPermission.onProvisionalCallback(() {
      setState(() => notificationState = PermissionStatus.provisional);
    });
    _notificationPermission.onRestrictedCallback(() {
      setState(() => notificationState = PermissionStatus.restricted);
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                for (final permissionEntry
                    in {
                      _locationPermission: locationState,
                      _notificationPermission: notificationState,
                    }.entries)
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text("${permissionEntry.key}: ${permissionEntry.value}"),
                      TextButton(
                        onPressed: () {
                          permissionEntry.key.request();
                        },
                        child: Text("REQUEST ${permissionEntry.key}"),
                      ),
                    ],
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Let me know if I'm doing anything wrong here (and if I am, why it's wrong and how to do it right).

Also, you mention the example app, but the example app in permission_handler/example doesn't feature any permission callbacks. To be clear, this error only happens when you have permission callbacks for two or more permissions.

@github-actions github-actions bot removed the status: needs more info We need more information before we can continue work on this issue. label Mar 18, 2025
@12people
Copy link

Here's a recording of the new code:

Screen.Recording.2025-03-18.at.11.07.51.mov

Notice how the notification-related callbacks are run when the location permission is set, rather than the location-related callbacks that should be run.

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

No branches or pull requests

3 participants