2

I've been trying to save a file, more precisely an image, in a shared folder ("Downloads", "Documents", etc...), but it seems that I'm missing something.

This is the MainPage.cs:

private async void GenerateQRCode()
{
    // Generate QR Code
    QRCodeGenerator qrCodeGenerator = new QRCodeGenerator();
    QRCodeData qrCodeData = qrCodeGenerator.CreateQrCode(ShopName.ToUpper() + ShopLocation.ToUpper(), QRCodeGenerator.ECCLevel.Q);
    PngByteQRCode qRCode = new PngByteQRCode(qrCodeData);
    byte[] qrCodeBytes = qRCode.GetGraphic(20);

    // Set the new image source to be the QR Code
    QRCodeImage.Source = ImageSource.FromStream(() => new MemoryStream(qrCodeBytes));

    // Get the permissions
    PermissionStatus statusWrite = await Permissions.RequestAsync<Permissions.StorageWrite>();
    PermissionStatus statusRead = await Permissions.RequestAsync<Permissions.StorageRead>();

    // Debug the permissions
    await DisplayAlert("Write", statusWrite.ToString(), "OK");
    await DisplayAlert("Read", statusRead.ToString(), "OK");

    // Get the Downloads path (only Android)
    string path = (string)Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads);
    
    // Get the final file name
    string fileName = Path.Combine(path, "QRCode.png");

    // Debug the file name
    await DisplayAlert("Debug", fileName, "OK");

    // Save the QR Code as a byte[]
    File.WriteAllBytes(path, qrCodeBytes);
}

These are the permissions in the AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

When I run this code I get this error:

System.UnauthorizedAccessException: 'Access to the path '/storage/emulated/0/Download' is denied.'

I've read that from Android's API 23 the user has to accept the permissions at run-time, but I've tried many solutions, but it still tells me that the access is denied. (The debug messages I get from the code I've posted are (Granted, Granted and /storage/emulated/0/Download/QRCode.png).

I've also read that Android.OS.Environment.GetExternalStoragePublicDirectory is deprecated, so if anyone has a "newer" way to get the desired directory, feel free to suggest!

UPDATE - MY SOLUTION:

Create a service interface in your "Global Project" (not only in the Android or in the iOS project) to make it more tidy:

public interface IFileService
{
    void Save(byte[] data, string name);
}

Create a new DependencyService in your Android project:

[assembly: Xamarin.Forms.Dependency(typeof(ProjectNamespace.Droid.FileService))]
namespace ProjectNamespace.Droid
{
    public class FileService : IFileService
    {
        public void Save(byte[] data, string name)
        {
            if (MainActivity.Instance.CheckSelfPermission(Manifest.Permission.WriteExternalStorage) == Android.Content.PM.Permission.Granted)
            {
                string path = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath;
                string filePath = System.IO.Path.Combine(path, name);
                FileOutputStream fileOutputStream = new FileOutputStream(new Java.IO.File(filePath));
                fileOutputStream.Write(data);
                fileOutputStream.Close();
            }
            else
            {
                MainActivity.Instance.RequestPermissions(new string[] { Manifest.Permission.WriteExternalStorage }, 0);
            }
        }
    }
}

Finally, in order to use MainActivity, we need a reference to it, and so I've created a Singleton (yeah, I know, I should have done it in a safer way, but since it was a learning project, I decided to make it in this way):


public static MainActivity Instance { get; private set; }

protected override void OnCreate(Bundle savedInstanceState)
{
    ...
    Instance = this;
    ...
}

Penca53
  • 80
  • 1
  • 8

2 Answers2

3

If you request the WriteExternalStorage run-time permission.It will work.

You could try it like below:

if (CheckSelfPermission(Manifest.Permission.WriteExternalStorage) == Android.Content.PM.Permission.Granted)
   {
            string path = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath;
            string filePath = System.IO.Path.Combine(path,"QRCode.png");         
            FileOutputStream fileOutputStream = new FileOutputStream(new Java.IO.File(filePath));               
            fileOutputStream.Write(qrCodeBytes);
            fileOutputStream.Close();
  }
  else
  {
      RequestPermissions(new string[] { Manifest.Permission.WriteExternalStorage }, 0);
  }   
Leo Zhu
  • 15,726
  • 1
  • 7
  • 23
  • 1
    Yes, it worked! I had to create a new `DependencyService`, so that I could call the `CheckSelfPermission` and `RequestPermissions` in the `MainActivity.cs` (Where I've created a Singleton). Apart from that, everything else was perfect! Thank you. – Penca53 Sep 22 '20 at 14:07
  • @Penca53 could you please update your post with the new code you used to make it work? I mean, the DependencyService creation and the way you check/request permissions, that's the point I'm blocked in – Windgate Jun 18 '21 at 09:43
  • @Windgate I've updated the question with my solution... let me know if it works! – Penca53 Jun 18 '21 at 21:43
  • This solution will not likely work when targeting Android 11 and higher which is now required to publish to Google Play. WRITE_EXTERNAL_STORAGE permission is now equivalent to MANAGE_EXTERNAL_STORAGE permission. Unless you can convince Google to make an exception for your app, it will not be allowed MANAGE_EXTERNAL_STORAGE permission. [Link](https://developer.android.com/about/versions/11/privacy/storage) – Bruce Haley Aug 04 '23 at 18:15
-1

Add EXTERNAL permissions.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Then save the files.

   string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
   string localFilename = "downloaded.jpg";
   string localPath = Path.Combine(documentsPath, localFilename);
   File.WriteAllBytes(localPath, bytes);

You can refer to Xamarin.Forms Saving file in filesystem

n-m
  • 37
  • 1
  • 1
  • 10
  • 1
    I've already added the permissions... Also, won't `Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);` save it in the application files, instead of the "global" Pictures folder? – Penca53 Sep 21 '20 at 19:00