I'm trying to have the app user select a folder to save pictures taken from the camera. If I need to use a different NuGet package than I'm using or a built-in feature, I can do that. The one I'm using was the first one I found that had a folder picker and I could get to work.
I've read through dozens of articles, Answers, Questions, tech docs, walkthroughs, watched videos, and more to try to get access to pick a folder for camera images to be saved. Unfortunately, nothing seems to be working, as I keep getting an Exception saying I don't have the "Storage" permission, even though I've tried several ways to get them. I'm also requesting pretty much every permission related to "Storage", even one that I don't think is a real permission, but it was referenced in one of those documents I read.
This works on 2 real devices running Android 9 (API 28) and Android 12 (API 31), but not on the Android 13 (API 33) device. I'm using the latest CommunityToolkit NuGet package for the FolderPicker.
Looking at the Permissions specifically for the app, there are no Permissions listed in the "Denied" section.
I know this has nothing to do with the custom way I'm checking permissions, since I use it for requesting Bluetooth and other permissions.
The "FolderPickerResult" line of code down near the bottom is what returns an exception. It doesn't throw it, but returns it to the "result" object/variable. The exception message is "Storage Permission is not granted."
This is the whole object that is returned:
{
FolderPickerResult {
Folder = ,
Exception = Microsoft.Maui.ApplicationModel.PermissionException: Storage permission is not granted.at CommunityToolkit.Maui.Storage.FolderPickerImplementation.InternalPickAsync(String initialPath, CancellationToken cancellationToken)in / _ / src / CommunityToolkit.Maui.Core / Essentials / FolderPicker / FolderPickerImplementation.android.cs: line 18 at CommunityToolkit.Maui.Storage.FolderPickerImplementation.PickAsync(String initialPath, CancellationToken cancellationToken)in / _ / src / CommunityToolkit.Maui.Core / Essentials / FolderPicker / FolderPickerImplementation.shared.cs: line 11,
IsSuccessful = False
}
}
As per the title, I'm using C# .Net 7 in a Maui project.
So, my questions are: What am I missing/doing wrong? Why doesn't this work when everything I'm reading says it should?
Here's my code, as minimal as possible:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.XXX.XXX" android:versionCode="1">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:supportsRtl="true" android:label="XXX">
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data>
</provider>
</application>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_STORAGE_PERMISSION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
</queries>
</manifest>
GalleryPermissions.cs
using static Microsoft.Maui.ApplicationModel.Permissions;
namespace Namespace;
internal class GalleryPermissions : BasePlatformPermission
{
#if ANDROID
public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
new List<(string permission, bool isRuntime)>
{
("android.permission.WRITE_EXTERNAL_STORAGE", true),
("android.permission.READ_EXTERNAL_STORAGE", true),
("android.permission.MANAGE_EXTERNAL_STORAGE", true),
("android.permission.MANAGE_MEDIA", true),
("android.permission.READ_MEDIA_IMAGES", true),
("android.permission.READ_MEDIA_AUDIO", true),
("android.permission.READ_MEDIA_VIDEO", true),
("android.permission.READ_STORAGE_PERMISSION", true),
("android.permission.MEDIA_CONTENT_CONTROL", true)
}.ToArray();
#endif
}
MainPage.xaml.cs
using CommunityToolkit.Maui.Storage;
protected override async void OnAppearing()
{
base.OnAppearing();
PermissionStatus storageWriteStatus = await CheckPermission<Permissions.StorageWrite>(); // always "denied"
PermissionStatus storageReadStatus = await CheckPermission<Permissions.StorageRead>(); // always "denied"
PermissionStatus galleryStatus = await CheckPermission<GalleryPermissions>(); // always "denied"
...
}
private static async Task<PermissionStatus> CheckPermission<TPermission>() where TPermission : Permissions.BasePermission, new()
{
PermissionStatus status = await Permissions.CheckStatusAsync<TPermission>();
if (status != PermissionStatus.Granted)
{
status = await Permissions.RequestAsync<TPermission>();
}
return status;
}
private async void SelectButton_Clicked(object sender, EventArgs e)
{
CancellationTokenSource source = new();
// SaveLocation.Text is originally string.Empty, but even if I put in a breakpoint and set
// it to the same "/storage/emulated/0/DCIM/Camera" that I get returned on other devices,
// I still get an Exception and result.IsSuccessful is `false`
FolderPickerResult result = await FolderPicker.PickAsync(SaveLocation.Text, source.Token);
//FolderPickerResult result = await FolderPicker.Default.PickAsync(SaveLocation.Text, source.Token); // This is the same as the line above
if (result.IsSuccessful)
{
SaveLocation.Text = result.Folder.Path;
}
else
{
// On API 33, this always prints "Storage Permission is not granted.", except when
// I try to remove permissions I've read aren't needed for API 33.
await Toast.Make($"{result.Exception.Message}").Show(source.Token);
Error.Text = result.Exception.Message;
}
}
Here are screenshots from the actual, real Android device I'm using to debug my app and running Android 13 (API 33), which happens to be a Samsung Galaxy A03s Tracfone. I've included the Permissions page for the App.