Penca53
Penca53

Reputation: 80

How to properly save a file in a shared folder in Xamarin?

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;
    ...
}

Upvotes: 2

Views: 7408

Answers (2)

Leo Zhu
Leo Zhu

Reputation: 14956

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);
  }   

Upvotes: 3

n-m
n-m

Reputation: 37

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

Upvotes: -1

Related Questions