checkAndRequest static method
Request permissions with smart dialog behavior.
Returns true if all requested permissions are granted or limited.
Returns false if any permission is not granted.
Behavior:
- Check the current status of each permission via
checkPermissionStatuson the native side. On Android, this only reportspermanentlyDeniedfor permissions that have been requested at least once (persisted via SharedPreferences), avoiding false positives on devices like Huawei whereshouldShowRationalereturns false for never-requested permissions. - Request permissions via the normal flow, which on the native side
also uses
checkPermissionStatus— the samehasEverBeenRequestedguard ensures only previously-requested permissions can be reported aspermanentlyDenied. - Show settings dialog only when BOTH
checkandrequestreturnpermanentlyDenied. This filters out:- "Never asked" false positives: check → denied (not yet requested), request → any status → no dialog.
- "User just chose Don't Ask Again": check → denied (shouldShow was true), request → permanentlyDenied → no dialog (respects user's immediate choice).
context is required for showing the permission dialog.
permissionTypes is the list of permissions to request.
Implementation
static Future<bool> checkAndRequest(
BuildContext context,
List<PermissionType> permissionTypes,
) async {
if (kIsWeb) {
return true;
}
// Step 1: Check current status.
bool checkReturnedPermanentlyDenied = false;
for (final type in permissionTypes) {
final status = await Permission.check(type);
if (status == PermissionStatus.permanentlyDenied) {
checkReturnedPermanentlyDenied = true;
break;
}
}
// Step 2: Always request — this serves as either the normal permission
// request or a probe to verify a real permanentlyDenied.
Map<PermissionType, PermissionStatus> statusMap = await Permission.request(permissionTypes);
bool allGranted = permissionTypes.every((type) {
final status = statusMap[type] ?? PermissionStatus.denied;
return status == PermissionStatus.granted || status == PermissionStatus.limited;
});
if (allGranted) {
return true;
}
// Step 3: Show settings dialog only when BOTH conditions are met:
// a) check() returned permanentlyDenied (pre-request signal)
// b) request() also returned permanentlyDenied (post-request confirmation)
// This filters out the false-positive "never asked" case: if the permission
// was truly never asked, request() will trigger the system dialog and return
// granted/denied (not permanentlyDenied), so condition (b) won't be met.
//
// It also handles the "user just chose Don't Ask Again" case: check()
// returned denied (shouldShow was true), so condition (a) won't be met,
// and we return silently — respecting the user's immediate choice.
if (checkReturnedPermanentlyDenied) {
bool requestReturnedPermanentlyDenied = permissionTypes.any((type) {
final status = statusMap[type] ?? PermissionStatus.denied;
return status == PermissionStatus.permanentlyDenied;
});
if (requestReturnedPermanentlyDenied && context.mounted) {
// Find the first permanently denied permission type for the dialog message.
final permanentlyDeniedType = permissionTypes.firstWhere(
(type) => (statusMap[type] ?? PermissionStatus.denied) == PermissionStatus.permanentlyDenied,
orElse: () => permissionTypes.first,
);
final bool shouldOpenSettings = await showPermissionDialog(context, permanentlyDeniedType);
if (shouldOpenSettings) {
await Permission.openAppSettings();
}
}
}
return false;
}