flutter-permission-handler
flutter-permission-handler copied to clipboard
Android 13 Permission.notification returns denied
🐛 Bug Report
If the user cancels notification permission request several times then the next time we get a denied
value when check permissionStatus
.
Although, we can no longer ask for permission. And we need it to be permanentlyDenied
Otherwise, it turns out that the interface has a button "allow access to notifications" but when you click on it nothing happens
Expected behavior
If the user has canceled the access request several times, I want to get the status permanentlyDenied
Reproduction steps
Take Android 13 and reject the notification access request several times
Configuration
Version: 10.0.0
Platform:
- [ ] :iphone: iOS
- [x] :robot: Android
Otherwise, it turns out that the interface has a button "allow access to notifications" but when you click on it nothing happens
I encountered an issue that sounds similar, unrelated to this plugin, which I think the Android 13 docs communicate poorly.
Using
-
targetSdkVersion 33
-
without
<uses-permission ... POST_NOTIFICATIONS" />
inAndroidManifest.xml
,
then a
- new app install
- on an Android 13 device
has its system settings toggle (App Info -> Permissions -> Notifications) disabled. (edit: Not just off. Disabled.)
A workaround is to roll back targetSdk to 32, and (/or?) add the manifest permission.
A temp workaround is to downgrade your targetSdkVersion to 32, while keeping compileSdkVersion on 33 to cater for firebase requirements.
Otherwise, it turns out that the interface has a button "allow access to notifications" but when you click on it nothing happens
I encountered an issue that sounds similar, unrelated to this plugin, which I think the Android 13 docs communicate poorly.
Using
targetSdkVersion 33
- without
<uses-permission ... POST_NOTIFICATIONS" />
inAndroidManifest.xml
,then a
- new app install
- on an Android 13 device
has its system settings toggle (App Info -> Permissions -> Notifications) disabled.
A workaround is to roll back targetSdk to 32, and (/or?) add the manifest permission.
Android 13 has the notifications permissions switched off by default as described here https://developer.android.com/develop/ui/views/notifications/notification-permission#new-apps.
But to specify this issue a little bit more:
When using await Permission.notification.request()
the correct behavior can be observed for android 13. After denying the permission for a second time the request dialogue won't show up anymore and the status permanentlyDenied
is true will be returned.
But if I try to just get the status via Permission.notification.status.isPermanentlyDenied
or Permission.notification.isPermanentlyDenied
right after I denied the Dialogue for a second time isPermanentlyDenied
will be false, expected behavior ist that isPermanentlyDenied
is true.
I am facing the same issue. The notification request pops up when the app is in the background and a notification is delivered.
I'm also having the same problem with Permission.storage, after denying a second time I always get isDenied
true and isPermanentlyDenied
false
I have the same behavior on iOS when I check the notification permission. Always returns denied
even though I granted
Has anyone found a fix for this? I'm experiencing the same issue. The notification request dialogue isn't showing up, but the logic to handle that is thrown off by the fact that the status is denied
rather than permanentlyDenied
.
Faced with same issue:(
Hello! I think that I found bug in this approach.
If I will not click on buttons in system dialog and just close dialog via click outside of dialog, on third iteration permission.request();
will show system dialog and then before
and after
will be both false, as a result, we will end up in a block with print('No more permission pop-ups displayed');
Do anyone have any idea what we can do with that?
@sanekyy When you reached that specific situation, you can just open the app settings programmatically. e.g. using the function openAppSettings
of the pub.dev package app_settings
Problem is that in that case user see system dialog first, skip then and there we have to end flow.
But we open settings:(
Same issue
I don't want to show a message to the user if he make a decision to disable notifications permanently. My temporary solution is:
- Save a flag in preferences as soon as Permission.notification.request() returns isPermanentlyDenied status.
- Call openAppSettings() when previous status is denied and new status (after call request() ) is isPermanentlyDenied, and when it was not a long time between calling Permission.notification.status and Permission.notification.request()
So, when user click the button 'Allow access', he will see notifications dialog or app settings. And then, I don't show the message and the button if Permission.notification.status is disabled and the flag from permissions is true.
Main disadvantages are:
- If a user clicks "deny" very fast, openSettings() will be call just after dialog complete
- If request() is slow when it is permanently denied, it returns "denied" status
- app settings will be opened instead of usual dialog on the third interaction
It looks like this:
///Temporary fix https://github.com/Baseflow/flutter-permission-handler/issues/902
extension TemporaryNotificationsStatusSolution on Permission {
static const _permissionName = "notificationsPermanentlyDenied";
static const _diffInMills = 500;
Future<PermissionStatus> notificationStatus() async {
var permissionStatus = await Permission.notification.status;
if (!permissionStatus.isDenied || !(await _isAndroidSdk33())) {
return permissionStatus;
}
final preferences = await SharedPreferences.getInstance();
final flag = preferences.getBool(_permissionName);
if (flag == true && permissionStatus.isDenied) {
return PermissionStatus.permanentlyDenied;
}
return permissionStatus;
}
///returns null when it opens App Settings
Future<PermissionStatus?> notificationRequest() async {
if (!(await _isAndroidSdk33())) {
return await Permission.notification.request();
}
final checkStatusTime = DateTime.now();
final status = await Permission.notification.status;
final newStatus = await Permission.notification.request();
final newCheckStatusTime = DateTime.now();
Duration difference = newCheckStatusTime.difference(checkStatusTime);
logger.d(difference.inMilliseconds);
if (status.isDenied &&
newStatus.isPermanentlyDenied &&
difference.inMilliseconds < _diffInMills) {
final preferences = await SharedPreferences.getInstance();
preferences.setBool(_permissionName, true);
openAppSettings();
return null;
}
return newStatus;
}
Future<bool> _isAndroidSdk33() async {
if (defaultTargetPlatform != TargetPlatform.android) {
return false;
}
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
return androidInfo.version.sdkInt == 33;
}
}
And then I call Permission.notification.notificationStatus(); instead of Permission.notification.status and Permission.notification.notificationRequest(); instead of Permission.notification.request()
For example:
class _NotificationPermissionsExampleState
extends State<NotificationPermissionsExample> with WidgetsBindingObserver {
bool _needToShow = false;
@override
Widget build(BuildContext context) {
if (!_needToShow) {
return const Text(
"Permissions are granted, limited or permanently denied");
}
return Column(
children: [
const Text("Please grant location permissions"),
OutlinedButton(
onPressed: _requestPermissions,
child: const Text("Grant permissions"))
],
);
}
@override
void initState() {
_checkForPermissions();
WidgetsBinding.instance.addObserver(this);
super.initState();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_checkForPermissions();
}
}
Future<void> _checkForPermissions(
{PermissionStatus? permissionStatus}) async {
final status =
permissionStatus ?? await Permission.notification.notificationStatus();
setState(() {
_needToShow =
!status.isGranted && !status.isLimited && !status.isPermanentlyDenied;
});
}
Future<void> _requestPermissions() async {
final permissionStatus =
await Permission.notification.notificationRequest();
if (permissionStatus != null) {
_checkForPermissions(permissionStatus: permissionStatus);
}
}
}
This might help https://www.youtube.com/watch?v=uMvGpBOT0ZY
I am having other issue with respect to getting the status of the notification permission.
When the app installed first time, When i check for
final notificationStatus = await Permission.notification.status;
Excepted behaviour : Status should be UNKNOWN or not determined
Actual behaviour : Status always Denied.
I need the status as unknown in app first lunch to show some explanations before i show the OS permission pop-up.
@Avinash01111992 Hi!
Usually this task solves via bool pref in preferences manager.
@sanekyy Hello
Thank you for you'er reply.
What you mean by via bool pref in preferences manager
Can you eleborate more on this solution.
Do I need to store the permission status in the front end ?? How ? Because when the app starts up , Status should be returned as unknown that is not coming , and also I should know when permission permanently disabled.
@AvinashGowda-11, here's how I am handling it. Let me know if this works for you as well.
class _PermissionUtilities {
Future<bool> requestCameraPermission() async {
PermissionStatus result = await Permission.camera.request();
if (result.isGranted) {
return true;
} else if (Platform.isIOS || result.isPermanentlyDenied) {
HapticFeedback.mediumImpact();
return false;
} else {
return false;
}
}
Future<bool> requestMicrophonePermission() async {
PermissionStatus result = await Permission.microphone.request();
if (result.isGranted) {
return true;
} else if (Platform.isIOS || result.isPermanentlyDenied) {
HapticFeedback.mediumImpact();
return false;
} else {
return false;
}
}
Future<void> openAppSettings() {
return AppSettings.openAppSettings(
asAnotherTask: true,
);
}
Future<bool> requestLocationPermission() async {
Map<Permission, PermissionStatus> _statuses = await [
Permission.location,
Permission.locationAlways,
Permission.locationWhenInUse,
].request();
bool _isGranted = false;
_statuses.forEach(
(key, value) async {
final bool _granted = value.isGranted;
if (_granted) {
_isGranted = _granted;
}
},
);
return _isGranted;
}
Future<bool> requestPhotosPermission() async {
PermissionStatus result = PermissionStatus.granted;
// In Android we need to request the storage permission,
// while in iOS is the photos permission
if (Platform.isIOS) {
// result = await Permission.storage.request();
result = await Permission.photos.request();
}
if (result.isGranted || result.isLimited) {
return true;
} else if (Platform.isIOS || result.isPermanentlyDenied) {
HapticFeedback.mediumImpact();
return false;
} else {
return false;
}
}
}
// How I am using it: -
class Utilities {
static _PermissionUtilities permission = _PermissionUtilities();
}
// And how I access the functions: -
final bool _isApproved = await Utilities.permission.requestCameraPermission();
So, what I have done in the above code is that I created a static singleton class Utilities
and then accessing the permission request functions from the _PermissionUtilities
class. You can also understand the functionality from the function's code. In that class I am checking the permission on the basis of platform. I hope this works for you as well.
This is a common issue and unfortunately nothing we can resolve.
Androids native method to check permission status will on return granted
or denied
and provides not further information (e.g. if permissions are permanently denied). This is also explained on our Wiki page.
The recommended way to handle this is to check permissions and if the result is PermissionStatus.denied
go ahead and request permissions. When permissions are permanently denied, Android will return immediately with the PermisisonStatus.permanentlyDenied
status and not show any dialog to the user.
This is a common issue and unfortunately nothing we can resolve.
Androids native method to check permission status will on return
granted
ordenied
and provides not further information (e.g. if permissions are permanently denied). This is also explained on our Wiki page.The recommended way to handle this is to check permissions and if the result is
PermissionStatus.denied
go ahead and request permissions. When permissions are permanently denied, Android will return immediately with thePermisisonStatus.permanentlyDenied
status and not show any dialog to the user.
but on 13 and higher, permanentlyDenied only return by request() prompt if user has denied the very first one. if first time user chose allow, request() can never return permanentlyDenied again. even manually change permission to deny and deny again from request() prompt in app.