1

I'm writing a MAUI app that fetches an image from a remote API and would like to save it to the device's storage. The problem I'm facing is with it working on Android API 33.

As the permissions changed, the FileSaver class from the MAUI Community Toolkit still demands the WRITE_EXTERNAL_STORAGE permission, which no longer exists, so it serves DENIED as default. I've seen people mentioning that you can downgrade to a lower API, but with Google's change to their Play store policy, this is not an option.

Has anyone managed or knows how I could get the FileSaver working on API 33 with .NET 7 or is there another way to save files so they could be accessed outside the app?

This is my Android manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="si.stroka.signature365" android:versionName="1">
    <application android:allowBackup="true" android:icon="@mipmap/appicon" android:supportsRtl="true" android:label="Signature365 Android"></application>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS_PRIVILEGED" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.ACCOUNT_MANAGER" />
    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>

Thanks!

Edit: Adding source code for how I request FileSaver. The dialog doesn't appear at all, it just throws the Storage permission not granted message in the exception. I was able to get it to work setting target SDK to 31, but that isn't an option for my app with Google Play Store demanding 33 in August...

 using var stream = new MemoryStream(Encoding.Default.GetBytes("Hello from the Community Toolkit!"));
    var fileSaverResult = await FileSaver.Default.SaveAsync("test.txt", stream, token.Token);
    if (fileSaverResult.IsSuccessful)
    {
        await Toast.Make($"The file was saved successfully to location: {fileSaverResult.FilePath}").Show(token.Token);
    }
    else
    {
        await Toast.Make($"The file was not saved successfully with error: {fileSaverResult.Exception.Message}").Show(token.Token);
    }
primix
  • 355
  • 1
  • 8
  • Add to question the actual line of code. And the complete file path you are saving to. Search for earlier stackoverflow posts about Android 13 file access. Unless it is a path private to app, you may need additional interaction with user, to get permission. (I’m not familiar with FileSaver class). I see from doc that file saver asks user for permission, so I would expect it to be up-to-date with 13. – ToolmakerSteve Aug 08 '23 at 17:43
  • Those permissions are ignored on Android 13, so they are not the problem. IIRC, 13 should bring up file search, then ask user to confirm permission to access the selected folder. Then it should show files in folder. Does it get that far? Just fails afterward when actually accessing the file? – ToolmakerSteve Aug 08 '23 at 17:51
  • 1
    Added more source code plus an explanation of my issue. As far as I can see in the source code of the community toolkit (https://github.com/CommunityToolkit/Maui/blob/f7651325c2a3af0a40bfc33e3a48caf5ef497f40/src/CommunityToolkit.Maui.Core/Essentials/FileSaver/FileSaverImplementation.android.cs#L17C1-L17C1), if the permission is not granted (which by default is denied on Android 13), the library throws an exception – primix Aug 10 '23 at 07:27
  • Found a workaround https://github.com/CommunityToolkit/Maui/issues/998 – Nutzs Aug 12 '23 at 16:11

1 Answers1

1

I was able to get this to work by referencing a workaround found here https://github.com/CommunityToolkit/Maui/issues/998

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:allowBackup="true" android:icon="@mipmap/appicon" android:supportsRtl="true"></application>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
    <queries>
        <intent>
            <action android:name="android.intent.action.SENDTO" />
            <data android:scheme="mailto" />
        </intent>
    </queries>
</manifest>

ContentPage or ContentView code behind

#if ANDROID
using Android;
using Android.Content.PM;
using AndroidX.Core.App;
using AndroidX.Core.Content;
#endif

Using the FileSaver in your page or view

// this will run for Android 33 and greater
            if (DeviceInfo.Platform == DevicePlatform.Android && OperatingSystem.IsAndroidVersionAtLeast(33))
            {
#if ANDROID
                var activity = Platform.CurrentActivity ?? throw new NullReferenceException("Current activity is null");
                if (ContextCompat.CheckSelfPermission(activity, Manifest.Permission.ReadExternalStorage) != Permission.Granted)
                {
                    ActivityCompat.RequestPermissions(activity, new[] { Manifest.Permission.ReadExternalStorage }, 1);
                }
#endif
            }

            var fileSaverResult = await FileSaver.Default.SaveAsync(attachment.AttachmentName, stream, cancellationToken);
Nutzs
  • 79
  • 5