Android's permission dialog gives the user the option to "Don't Ask Again".
I must know, through the PermissionStatus enum, if the user selected that option. That's because, if I can't ask again, I must tell the user to go to the device's settings screen to give me the necessary permissions.
I suggest creating PermissionStatus.dontAskAgain.
Ibid.
selected No again ask , Then refuse, return content, versus Do not choose No again ask, return content same, Can't distinguish whether it is checked or not.
@celiyawong That's exactly my point.
@hugocbpassos, @celiyawong, you can use the shouldShowRequestPermissionRationale method to figure this out.
This method returns true if the user has previously denied the request. But if the user has selected “Dont’ ask again” this method would always return false. It also returns false if we are prompting for permission for the first time.
@mvanbeusekom So the only way I can be 100% sure that the user selected "Don't ask again" is requesting the permission, checking if it's denied and then checking if shouldShowRequestPermissionRationale returns false, right? If I just want to check the permission, the same logic wouldn't work because if I haven't requested the permission at least once, it'll be denied and shouldShowRequestPermissionRationale would return false either. I would have to rely on my own code to know if the permission had been requested before or stop checking permissions, but always requesting.
I'm sorry, but you got to agree with me that this is a bad approach. Not only because it wouldn't work in the mentioned situation, but also because it makes things more complex and demands the plugin user to create more platform-specific code – if (Platform.isAndroid) doThis() else doThat().
I would be glad if you considered adding the dontAskAgain to PermissionStatus enum.
Please, feel free to correct me if something I said is wrong.
So to find out if the user selected “don’t ask again” (even the first time you are requesting permissions) is (this is on Android):
checkPermissionsdenied call the shouldShowPermissionRationale:false, the user selected "don’t ask again".true it is either the first time you are requesting permissions or the user denied permissions earlier but did NOT select the “don’t ask again” option. Either way it is save to continue requesting permissions.So you don’t have to “always” request permissions. I think I made a typo in the first reply where I said “it also returns false if we are prompting for permissions for the first time” this should have been true (although documentation is not very clear on this and I haven’t specifically tested it).
To my knowledge there is no other “direct” way on checking this on Android and this is how Google recommends it (please enlighten me if I am wrong). So if I were to solve it in the plugin, given my current understanding of the SDK, this would be the way I would implement it.
Either ways would be saving something to shared preferences and checking if we requested permissions before, however this feels more like something that the developer should decide for themselves.
@mvanbeusekom There are at least three ways to get a false shouldShowRequestPermissionRationale (didn't test service-related situations):
Checking a permission that has never been requested:
status: denied
shouldShowRequestPermissionRationale: false
Checking or requesting a permission marked as "Don't ask again":
status: denied
shouldShowRequestPermissionRationale: false
Checking or requesting a permission that has been granted:
status: granted
shouldShowRequestPermissionRationale: false
Please, feel free to test it:
void main() => runApp(Main());
class Main extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(body: Home()),
);
}
}
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
requestPermissionButton(),
checkPermissionButton(),
],
),
);
}
RaisedButton checkPermissionButton() {
return RaisedButton(
child: Text('Check permission'),
onPressed: checkPermissionAndPrintResult,
);
}
RaisedButton requestPermissionButton() {
return RaisedButton(
child: Text('Request permission'),
onPressed: requestPermissionAndPrintResult,
);
}
void checkPermissionAndPrintResult() async {
PermissionStatus status =
await PermissionHandler().checkPermissionStatus(PermissionGroup.microphone);
bool shouldShowRequestPermissionRationale =
await PermissionHandler().shouldShowRequestPermissionRationale(PermissionGroup.microphone);
print('status: $status');
print('shouldShowRequestPermissionRationale: $shouldShowRequestPermissionRationale');
}
void requestPermissionAndPrintResult() async {
Map<PermissionGroup, PermissionStatus> status =
await PermissionHandler().requestPermissions([PermissionGroup.microphone]);
bool shouldShowRequestPermissionRationale =
await PermissionHandler().shouldShowRequestPermissionRationale(PermissionGroup.microphone);
print('status: ${status[PermissionGroup.microphone]}');
print('shouldShowRequestPermissionRationale: $shouldShowRequestPermissionRationale');
}
}
Well, I can't distinguish between 1 and 2. As you said, seems like the only solution would be persisting if we requested permissions before, but I feel like this is a plugin responsibility. If you don't think so, at least the shouldShowRequestPermissionRationale function should have a really clear documentation, which explains the developer its responsibility.
You have convinced me that we could maybe do a better job here and make it easier for other developers ;).
This means that we will be introducing a breaking change (since the enum value might change overnight).
I had the exact same problem on Android and I came up with this idea: I'm checking how long it takes for the requestPermissions() method to return a value. If it's less than 100ms then I can be fairly certain it's an automatic response from the platform itself without any user interaction and I redirect the app to the Settings page, otherwise I parse the response from the requestPermissions() method.
That's a nice trick. I could use that while this issue is opened, but I wonder how effective it is in some low-range devices.
@tonisostrat That is an awesome way of getting around this - great work.
However, it would be great if we can get a Don't Ask Again flag as it is considered Android best practice to redirect users to the setting screen in this case.
To do this we would need to store a bool, for instance, in the SharedPreferences and be able to call shouldShowRequestPermissionRationale.
if shouldShowRequestPermissionRationale is false and it's been asked before (that's why we need the SharedPreferences), then it's been denied forever.
try this helper. It will answer "granted", "not granted" or "do not ask again".
I don't want to send a pull request because it implies another dependency (SharedPreferences)
If you think that can be added, let me know and I'll send a PR.
import 'package:flutter/foundation.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
enum MyAwesomeStatus { granted, notGranted, doNotAskAgain }
class PermissionHelper {
static Future<MyAwesomeStatus> askWriteExternalPermission(
TargetPlatform platform, PermissionGroup permissionGroup) async {
MyAwesomeStatus status;
if (platform == TargetPlatform.android) {
Map<PermissionGroup, PermissionStatus> permissionsGranted =
await PermissionHandler()
.requestPermissions(<PermissionGroup>[permissionGroup]);
PermissionStatus permissionStatus = permissionsGranted[permissionGroup];
if (permissionStatus == PermissionStatus.granted) {
status = MyAwesomeStatus.granted;
} else {
bool beenAsked = await hasPermissionBeenAsked(permissionGroup);
bool rationale = await PermissionHandler().shouldShowRequestPermissionRationale(permissionGroup);
if (beenAsked && !rationale) {
status = MyAwesomeStatus.doNotAskAgain;
} else {
status = MyAwesomeStatus.notGranted;
}
}
} else {
status = MyAwesomeStatus.granted;
}
setPermissionHasBeenAsked(permissionGroup);
return status;
}
static Future<void> setPermissionHasBeenAsked(
PermissionGroup permissionGroup) async {
(await SharedPreferences.getInstance())
.setBool('PERMISSION_ASKED_${permissionGroup.value}', true);
}
static Future<bool> hasPermissionBeenAsked(
PermissionGroup permissionGroup) async {
return (await SharedPreferences.getInstance())
.getBool('PERMISSION_ASKED_${permissionGroup.value}') ?? false;
}
}
This issue will be solved in our next release (4.2.0), where on Android the methods checkPermissionStatus and requestPermissions will return the neverAskAgain permission status when the user has denied the permission and selected the "Don't ask again" checkbox.
More info in PR #189.
Most helpful comment
You have convinced me that we could maybe do a better job here and make it easier for other developers ;).
This means that we will be introducing a breaking change (since the enum value might change overnight).