I was getting the same issue, I kept getting an unauthorized access exception on file writes even when writing to internal app data locations such as when trying to write to Xamarin.Essentials.FileSystem.AppDataDirectory
, Xamarin.Essentials.FileSystem.CacheDirectory
or System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal)
.
It seems using Java.IO.File instead of System.File fixed the issue on Android for me.
I saved to internal App storage root = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
since I only needed the file in my own app, but it seems like root = Android.OS.Environment.ExternalStorageDirectory.ToString();
doesn't throw an exception either. Haven't tested the external storage method much though.
I created this class in my Xamarin.Android project:
[assembly: Xamarin.Forms.Dependency(typeof(FileStorageService))]
namespace MyApp.Droid.Services
{
public class FileStorageService: IFileStorageService
{
private string _rootPath;
private const string _appFolder = "MyAppFolder";
public FileStorageService()
{
var root = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
_rootPath = $"{root}/{_appFolder}";
}
public async Task WriteFile(string fileName, MemoryStream stream, string relativePath = "")
{
try
{
if(!relativePath.IsNullOrEmpty() && relativePath?[0] != '/')
{
relativePath = $"/{relativePath}";
}
var fileDirectory = new Java.IO.File($"{_rootPath}{relativePath}");
fileDirectory.Mkdir();
var file = new Java.IO.File(fileDirectory, fileName);
if (file.Exists())
{
file.Delete();
}
using (var outputStream = new FileOutputStream(file))
{
await outputStream.WriteAsync(stream.ToArray());
outputStream.Flush();
outputStream.Close();
}
}
catch (Exception ex)
{
System.Console.WriteLine(ex.ToString());
}
}
public async Task<byte[]> ReadFile(string fileName, string relativePath = "")
{
try
{
if (!relativePath.IsNullOrEmpty() && relativePath?[0] != '/')
{
relativePath = $"/{relativePath}";
}
var fileDirectory = new Java.IO.File($"{_rootPath}{relativePath}");
var file = new Java.IO.File(fileDirectory, fileName);
if (!file.Exists())
{
return null;
}
byte[] fileContent = new byte[file.Length()];
using (var inputStream = new FileInputStream(file))
{
await inputStream.ReadAsync(fileContent);
inputStream.Close();
}
return fileContent;
}
catch (Exception ex)
{
System.Console.WriteLine(ex.ToString());
}
}
}
}
And its interface in my Xamarin.Forms project:
public interface IFileStorageService
{
Task WriteFile(string fileName, MemoryStream stream, string relativePath = "");
Task<byte[]> ReadFile(string fileName, string relativePath = "");
}
Where you want to save your file in your shared code:
await DependencyService.Get<IFileStorageService>().WriteFile(fileName, memoryStream, relativePath);
Where you want to read your file in your shared code:
var fileContent = await DependencyService.Get<IFileStorageService>().ReadFile(fileName, relativePath);
Hope this helps!