[Bug]: Callbacks like onGrantedCallback does not work as expected.
Please check the following before submitting a new issue.
- [X] I have searched the existing issues.
- [X] I have carefully read the documentation and verified I have added the required platform specific configuration.
Please select affected platform(s)
- [X] Android
- [ ] iOS
- [ ] Windows
Steps to reproduce
- Use a callback
Permission.contacts.onGrantedCallback - Grant permission for camera.
Permission.contacts.onGrantedCallbackwill 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!
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,
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.
@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
Here's a recording of the odd behavior:
https://github.com/user-attachments/assets/440cd356-223f-4ec4-bdd6-672aad6e29d0
(Same code as I posted above.)
As for my Android version, it's Android 13 (build 6.A.031.7).
@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,
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.
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,
I will provide you a minimal reproducible code when I am free.
@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.
Here's a recording of the new code:
https://github.com/user-attachments/assets/c46f5544-43ae-4711-a548-d8d0258f2777
Notice how the notification-related callbacks are run when the location permission is set, rather than the location-related callbacks that should be run.
@TimHoogstrate Any updates on this? Anything else I should provide?
@12people,
Can you verify this with the Android example app? I cannot reproduce this in the example app, so it should be something in your code (or any other factor).
Kind regards,
@TimHoogstrate I addressed this in a previous comment:
Also, you mention the example app, but the example app in
permission_handler/exampledoesn't feature any permission callbacks. To be clear, this error only happens when you have permission callbacks for two or more permissions.
So I'm not sure how I could reproduce this there other than by adding the same kind of code as in my example above.
Hi everyone,
I'm facing the same issue described in this thread, and I wanted to provide more details regarding what seems to be happening.
When registering multiple onXXXCallback functions, only the last one registered gets executed, regardless of which permission is actually granted.
For example:
_locationPermission.onGrantedCallback(() {
setState(() => locationState = PermissionStatus.granted);
});
_notificationPermission.onGrantedCallback(() {
setState(() => notificationState = PermissionStatus.granted);
});
In this case, only the callback for _notificationPermission is called, even if the user grants location permission. This makes it impossible to handle multiple permissions with individual callbacks properly, as earlier registered callbacks are essentially ignored or overwritten.
It looks like the onGrantedCallback is not scoped per permission instance as expected, but rather globally shared or overwritten internally.
Would appreciate any updates or potential workarounds from the maintainers or community.
Thanks!
Hi @romainpurchla
Not sure but perhaps doing like the documentation :
// You can request multiple permissions at once.
Map<Permission, PermissionStatus> statuses = await [
Permission.location,
Permission.storage,
].request();
print(statuses[Permission.location]);
@Cyrille37 That solves asking for multiple permissions, but not watching the changing state of those permissions via callbacks.
My workaround so far has been to have a family notifier in Riverpod to keep track of the permission state, and always requesting permissions through it. (However, unfortunately, it doesn't track the state if the user changes the permissions outside of the app UI -- e.g. via system app settings -- while the app is running.)