Permissions

Browse sample. Browse the sample

This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) Permissions class. This class allows you to check and request permissions at run-time. The Permissions type is available in the Microsoft.Maui.ApplicationModel namespace.

Available permissions

.NET MAUI attempts to abstract as many permissions as possible. However, each operating system has a different set of permissions. Even though the API allows access to a common permission, there may be differences between operating systems related to that permission. The following table describes the available permissions:

The following table uses ✔️ to indicate that the permission is supported and ❌ to indicate the permission isn't supported or isn't required:

Permission Android iOS Windows tvOS
Battery ✔️
Bluetooth ✔️
CalendarRead ✔️ ✔️
CalendarWrite ✔️ ✔️
Camera ✔️ ✔️
ContactsRead ✔️ ✔️
ContactsWrite ✔️ ✔️
Flashlight ✔️
LocationWhenInUse ✔️ ✔️ ✔️
LocationAlways ✔️ ✔️
Media ✔️
Microphone ✔️ ✔️
NearbyWifiDevices ✔️
NetworkState ✔️
Phone ✔️ ✔️
Photos ✔️ ✔️
PhotosAddOnly ✔️ ✔️
PostNotifications ✔️
Reminders ✔️
Sensors ✔️ ✔️
Sms ✔️ ✔️
Speech ✔️ ✔️
StorageRead ✔️
StorageWrite ✔️
Vibrate ✔️

Important

The StorageRead and StorageWrite permissions will always return Granted on Android API 33+. This is because the underlying Android READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions are no longer available from API 33.

If a permission is marked as ❌, it will always return Granted when checked or requested.

Checking permissions

To check the current status of a permission, use the Permissions.CheckStatusAsync method along with the specific permission to get the status for. The following example checks the status of the LocationWhenInUse permission:

PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();

A PermissionException is thrown if the required permission isn't declared.

It's best to check the status of the permission before requesting it. Each operating system returns a different default state, if the user has never been prompted. iOS returns Unknown, while others return Denied. If the status is Granted then there's no need to make other calls. On iOS if the status is Denied you should prompt the user to change the permission in the settings. On Android, you can call ShouldShowRationale to detect if the user has already denied the permission in the past.

Permission status

When using CheckStatusAsync or RequestAsync, a PermissionStatus is returned that can be used to determine the next steps:

  • Unknown
    The permission is in an unknown state, or on iOS, the user has never been prompted.

  • Denied
    The user denied the permission request.

  • Disabled
    The feature is disabled on the device.

  • Granted
    The user granted permission or is automatically granted.

  • Restricted
    In a restricted state.

  • Limited
    In a limited state. Only iOS returns this status.

Requesting permissions

To request a permission from the users, use the RequestAsync method along with the specific permission to request. If the user previously granted permission, and hasn't revoked it, then this method will return Granted without showing a dialog to the user. Permissions shouldn't be requested from your MauiProgram or App class, and should only be requested once the first page of the app has appeared.

The following example requests the LocationWhenInUse permission:

PermissionStatus status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();

A PermissionException is thrown if the required permission isn't declared.

Important

On some platforms, a permission request can only be activated a single time. Further prompts must be handled by the developer to check if a permission is in the Denied state, and then ask the user to manually turn it on.

Explain why permission is needed

It's best practice to explain to your user why your application needs a specific permission. On iOS, you must specify a string that is displayed to the user. Android doesn't have this ability, and also defaults permission status to Disabled. This limits the ability to know if the user denied the permission or if it's the first time the permission is being requested. The ShouldShowRationale method can be used to determine if an informative UI should be displayed. If the method returns true, this is because the user has denied or disabled the permission in the past. Other platforms always return false when calling this method.

Example

The following code presents the general usage pattern for determining whether a permission has been granted, and then requesting it if it hasn't.

public async Task<PermissionStatus> CheckAndRequestLocationPermission()
{
    PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();

    if (status == PermissionStatus.Granted)
        return status;

    if (status == PermissionStatus.Denied && DeviceInfo.Platform == DevicePlatform.iOS)
    {
        // Prompt the user to turn on in settings
        // On iOS once a permission has been denied it may not be requested again from the application
        return status;
    }

    if (Permissions.ShouldShowRationale<Permissions.LocationWhenInUse>())
    {
        // Prompt the user with additional information as to why the permission is needed
    }

    status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();

    return status;
}

Extending permissions

The Permissions API was created to be flexible and extensible for applications that require more validation or permissions that aren't included in .NET MAUI. Create a class that inherits from Permissions.BasePermission, and implement the required abstract methods. The following example code demonstrates the basic abstract members, but without implementation:

public class MyPermission : Permissions.BasePermission
{
    // This method checks if current status of the permission.
    public override Task<PermissionStatus> CheckStatusAsync()
    {
        throw new System.NotImplementedException();
    }

    // This method is optional and a PermissionException is often thrown if a permission is not declared.
    public override void EnsureDeclared()
    {
        throw new System.NotImplementedException();
    }

    // Requests the user to accept or deny a permission.
    public override Task<PermissionStatus> RequestAsync()
    {
        throw new System.NotImplementedException();
    }

    // Indicates that the requestor should prompt the user as to why the app requires the permission, because the
    // user has previously denied this permission.
    public override bool ShouldShowRationale()
    {
        throw new NotImplementedException();
    }
}

When implementing a permission in a specific platform, the Permissions.BasePlatformPermission class can be inherited from. This class provides extra platform helper methods to automatically check the permission declarations. This helps when creating custom permissions that do groupings, for example requesting both Read and Write access to storage on Android. The following code example demonstrates requesting Read and Write storage access:

public class ReadWriteStoragePerms : Permissions.BasePlatformPermission
{
    public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
        new List<(string androidPermission, bool isRuntime)>
        {
        (global::Android.Manifest.Permission.ReadExternalStorage, true),
        (global::Android.Manifest.Permission.WriteExternalStorage, true)
        }.ToArray();
}

You then check the permission in the same way as any other permission type provided by .NET MAUI:

PermissionStatus status = await Permissions.RequestAsync<ReadWriteStoragePerms>();

If you wanted to call this API from your cross-platform code, you could create an interface and register the custom permission as a dependency in the app's service container. The following example shows the IReadWritePermission interface:

public interface IReadWritePermission
{        
    Task<PermissionStatus> CheckStatusAsync();
    Task<PermissionStatus> RequestAsync();
}

Then implement the interface in your custom permission:

public class ReadWriteStoragePermission : Permissions.BasePlatformPermission, IReadWritePermission
{
    public override (string androidPermission, bool isRuntime)[] RequiredPermissions => new List<(string androidPermission, bool isRuntime)>
    {
        (Android.Manifest.Permission.ReadExternalStorage, true),
        (Android.Manifest.Permission.WriteExternalStorage, true)
    }.ToArray();
}

In the MauiProgram class you should then register the interface and its concrete type, and the type that will consume the custom permission, in the app's service container:

builder.Services.AddTransient<MyViewModel>();
builder.Services.AddSingleton<IReadWritePermission, ReadWriteStoragePermission>();

The custom permission implementation can then be resolved and invoked from one of your types, such as a viewmodel:

public class MyViewModel
{
    IReadWritePermission _readWritePermission;

    public MyViewModel(IReadWritePermission readWritePermission)
    {
        _readWritePermission = readWritePermission;
    }

    public async Task CheckPermissionAsync()
    {
        var status = await _readWritePermission.CheckStatusAsync();
        if (status != PermissionStatus.Granted)
        {
            status = await _readWritePermission.RequestAsync();
        }
    }
}

Platform differences

This section describes the platform-specific differences with the permissions API.

Permissions must have the matching attributes set in the Android Manifest file. Permission status defaults to Denied.