Reputation: 2453
I'm looking for an easy way to query for and ask for multiple permissions in .Net Maui. I also need to request Bluetooth permissions, but Maui doesn't have that built-in, so far as I can see. The last I heard from 2022 was that Bluetooth wasn't in the Maui roadmap, which I find to be pretty shortsighted.
I've read a bunch of articles, Q&A's, documentation, watched videos, and done lots of other research to figure this out.
So far, I can check and request individual permissions, but I'm looking for something more DRY than repeating:
PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.XXX>();
if (status != PermissionStatus.Granted){
status = await Permissions.RequestAsync<Permissions.XXX>();
}
for every permission. I need several permissions, so my checks are quickly getting out of hand.
BTW, I'm targeting Android 13.0, as per Google Play requirements.
I know this is asking for a lot, but I'm getting lost in all the suggestions, many of which don't work, so help sorting out all this would be great!
Upvotes: 4
Views: 11831
Reputation: 2453
I've used many different sources to put this together, but these were the most useful.
Available Maui Permissions: https://learn.microsoft.com/en-us/dotnet/maui/platform-integration/appmodel/permissions
Permission on main thread: https://stackoverflow.com/a/75574570/1836461
Bluetooth and custom permissions: https://www.youtube.com/watch?v=9GljgwfpiiE
Location Services check: https://stackoverflow.com/a/69158717/1836461
To make this easier to follow, I'm going to leave all the code for last.
Also, my project is targeting .Net 7.0, the current Google Play requirements of Android 13.0 (API 33), iOS 11.0, and Windows 10.0.19041.0. I haven't checked if this works for iOS or Windows, but this at least gets you/me several steps towards running these other OSes. VS 2022 doesn't throw any error when changing the target OS for the JIT compiler, so it should work. If it doesn't, there should be fewer adjustment to do than suggestions from 1-5+ years ago or those written in Java specifically for Android.
You'll need to set up your Manifest and .plist files for the correct permissions for your needs. I'm not going to go through that here, since it's covered nicely in the first reference I've linked above.
I suggest putting your permission checking code into a helper method. Since this is going to be an async
method, you'll need to do call it from the "OnAppearing" method, which you'll have to override and make async
.
I made my helper method return a bool
, so I could check if all the permissions were accepted, since my app requires all the permissions I ask for. Without them, it simply wouldn't do anything. To easily check if the permissions were granted/limited, I added another method for that, since I'm checking so many permissions.
You can move the individual CheckStatusAsync
and RequestAsync
to a generic method and simply call that to help prevent repetition.
Since you/I need Bluetooth access, you'll have to write a custom permissions checker, but only for Android and not iOS or Windows. It's not hard, but there aren't many resources to show you how and they aren't easy to find, either.
Apparently, none of this matters for Bluetooth permissions if the Location Services on the phone aren't enabled. If your app can scan for Bluetooth devices, but none are found, it's a good bet the phone's Location Services aren't enabled. I've had to add a Toast to let the user know it's necessary when the scans return zero results.
Or you can check the Location Services status directly using OS specific methods.
MainPage.xaml.cs:
using CommunityToolkit.Maui.Alerts; // For the Toast
#if ANDROID
using Android.Content;
using Android.Locations;
#elif IOS || MACCATALYST
using CoreLocation;
#elif WINDOWS
using Windows.Devices.Geolocation;
#endif
protected override async void OnAppearing()
{
base.OnAppearing();
if (!await CheckPermissions())
{
await Toast.Make("Not all permissions were accepted. Application will close.").Show();
Application.Current.Quit();
}
}
private async Task<bool> CheckPermissions()
{
PermissionStatus bluetoothStatus = await CheckBluetoothPermissions();
PermissionStatus cameraStatus = await CheckPermissions<Permissions.Camera>();
PermissionStatus mediaStatus = await CheckPermissions<Permissions.Media>();
PermissionStatus storageWriteStatus = await CheckPermissions<Permissions.StorageWrite>();
//PermissionStatus photosStatus = await CheckPermissions<Permissions.Photos>();
...
bool locationServices = IsLocationServiceEnabled();
return IsGranted(cameraStatus) && IsGranted(mediaStatus) && IsGranted(storageWriteStatus) && IsGranted(bluetoothStatus);
}
private async Task<PermissionStatus> CheckBluetoothPermissions()
{
PermissionStatus bluetoothStatus = PermissionStatus.Granted;
if (DeviceInfo.Platform == DevicePlatform.Android)
{
if (DeviceInfo.Version.Major >= 12)
{
bluetoothStatus = await CheckPermissions<BluetoothPermissions>();
}
else
{
bluetoothStatus = await CheckPermissions<Permissions.LocationWhenInUse>();
}
}
return bluetoothStatus;
}
private async Task<PermissionStatus> CheckPermissions<TPermission>() where TPermission : Permissions.BasePermission, new()
{
PermissionStatus status = await Permissions.CheckStatusAsync<TPermission>();
if (status != PermissionStatus.Granted){
status = await Permissions.RequestAsync<TPermission>();
}
return status;
}
private static bool IsGranted(PermissionStatus status)
{
return status == PermissionStatus.Granted || status == PermissionStatus.Limited;
}
#if ANDROID
private bool IsLocationServiceEnabled()
{
LocationManager locationManager = (LocationManager)Android.App.Application.Context.GetSystemService(Context.LocationService);
return locationManager.IsProviderEnabled(LocationManager.GpsProvider);
}
#elif IOS || MACCATALYST
public bool IsLocationServiceEnabled()
{
return CLLocationManager.Status == CLAuthorizationStatus.Denied;
}
#elif WINDOWS
private bool IsLocationServiceEnabled()
{
Geolocator locationservice = new Geolocator();
return locationservice.LocationStatus == PositionStatus.Disabled;
}
#endif
Create a new file in your project called "BluetoothPermissions.cs":
using static Microsoft.Maui.ApplicationModel.Permissions;
namespace YourNamespace;
internal class BluetoothPermissions : BasePlatformPermission
{
#if ANDROID
public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
new List<(string permission, bool isRuntime)>
{
("android.permission.BLUETOOTH", true),
("android.permission.BLUETOOTH_ADMIN", true),
("android.permission.BLUETOOTH_SCAN", true),
("android.permission.BLUETOOTH_CONNECT", true),
("android.permission.ACCESS_COARSE_LOCATION", true),
("android.permission.ACCESS_FINE_LOCATION", true)
}.ToArray();
// This list includes Bluetooth LE permissions.
// You will need to include these permissions in your Android Manifest, too.
#endif
}
Notes: As I continue working on this app, I'm finding ways to use features without all the permissions needing to be required, like Bluetooth. As I make these features able to be turned on and off, I'm finding it increasingly likely that I will have to check for these permissions on settings pages other than the "MainPage". I'm probably going to end up turning most (if not all) of these permissions into it's own class so I can access it from the various places I'll be turning features on and off. If I do that, I'll leave this answer as is and make a new answer, if it's significantly different than this one.
Upvotes: 12