checkAndRequest static method

Future<bool> checkAndRequest(
  1. BuildContext context,
  2. List<PermissionType> permissionTypes
)

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:

  1. Check the current status of each permission via checkPermissionStatus on the native side. On Android, this only reports permanentlyDenied for permissions that have been requested at least once (persisted via SharedPreferences), avoiding false positives on devices like Huawei where shouldShowRationale returns false for never-requested permissions.
  2. Request permissions via the normal flow, which on the native side also uses checkPermissionStatus — the same hasEverBeenRequested guard ensures only previously-requested permissions can be reported as permanentlyDenied.
  3. Show settings dialog only when BOTH check and request return permanentlyDenied. 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;
}