| name | maui-permissions |
| description | .NET MAUI runtime permissions guidance — checking and requesting permissions, PermissionStatus handling, custom permissions via BasePlatformPermission, platform-specific manifest/plist declarations, and DI-friendly service patterns. USE FOR: "request permission", "check permission", "PermissionStatus", "runtime permission", "BasePlatformPermission", "custom permission", "Android manifest permission", "Info.plist permission", "permission denied handling". DO NOT USE FOR: geolocation-specific permissions (use maui-geolocation), camera/photo permissions (use maui-media-picker), or notification permissions (use maui-local-notifications).
|
.NET MAUI Permissions — Gotchas & Best Practices
Critical Anti-Patterns
1. Requesting without checking first
var status = await Permissions.RequestAsync<Permissions.Camera>();
var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
if (status != PermissionStatus.Granted)
status = await Permissions.RequestAsync<Permissions.Camera>();
2. Calling permissions in a constructor
Permission APIs are async and require a UI context. Constructors can't await.
public MyViewModel()
{
var status = Permissions.RequestAsync<Permissions.Camera>().Result;
}
protected override async void OnAppearing()
{
await CheckAndRequestPermissionAsync<Permissions.Camera>();
}
3. Ignoring Denied and Restricted status
if (status == PermissionStatus.Granted) { }
switch (status)
{
case PermissionStatus.Granted: break;
case PermissionStatus.Limited: break;
case PermissionStatus.Denied:
case PermissionStatus.Restricted:
await ShowSettingsPromptAsync(); break;
}
Platform Pitfalls
⚠️ iOS: One-shot permission dialog
iOS shows the system permission dialog only once per permission, ever. After denial, RequestAsync returns Denied immediately without showing UI. You must guide the user to Settings → App → Permission.
⚠️ Android: ShouldShowRationale timing
ShouldShowRationale returns true only after a prior denial (but not after "Don't ask again"). Use it to show explanatory UI before re-requesting:
if (Permissions.ShouldShowRationale<Permissions.Camera>())
{
await Shell.Current.DisplayAlert("Permission needed",
"Camera access is required to scan barcodes.", "OK");
}
status = await Permissions.RequestAsync<Permissions.Camera>();
⚠️ Android API 33+: StorageRead/StorageWrite are dead
On Android 13+, StorageRead and StorageWrite always return Granted (scoped storage makes them meaningless). Use granular media permissions instead:
await Permissions.RequestAsync<Permissions.StorageRead>();
await Permissions.RequestAsync<Permissions.Photos>();
await Permissions.RequestAsync<Permissions.Media>();
⚠️ Windows: Most permissions always return Granted
Windows doesn't have runtime permission dialogs for most features. Declare capabilities in Package.appxmanifest instead.
Always-Check-Before-Request Pattern
The recommended pattern handles iOS one-shot and Android rationale:
public async Task<PermissionStatus> CheckAndRequestPermissionAsync<T>()
where T : Permissions.BasePermission, new()
{
var status = await Permissions.CheckStatusAsync<T>();
if (status == PermissionStatus.Granted)
return status;
if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
return status;
if (Permissions.ShouldShowRationale<T>())
{
await Shell.Current.DisplayAlert("Permission needed",
"This feature requires the requested permission.", "OK");
}
return await Permissions.RequestAsync<T>();
}
Checklist