Forum Xamarin.Android
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

Saving files to public external storage on Android with an API level >=29

EitanEEitanE Member ✭✭

I'm trying to export files to the public external storage of an Android phone in Xamarin, for a backup DB. However, in the last version of Android phones (11.0 - API30), one can't opt-out of scoped storage using the property android:requestLegacyExternalStorage="true" of the <application> tag in the manifest.xml.

I made sure that the permissions READ_EXTERNAL_STORAGE & WRITE_EXTERNAL_STORAGE are granted before trying to create the file. Still, when trying to create a file, a System.UnauthorizedAccessException exception is thrown.

/* file 1: */
// ....
private async void Export_Tapped (object sender, EventArgs e) {
    // check if permission for writing in external storage is given
    bool canWrite = await FileSystem.ExternalStoragePermission_IsGranted ();

    if (!canWrite) {
        // request permission
        canWrite = await FileSystem.ExternalStoragePermission_Request ();

        if (!canWrite) {
            // alert user

            return;
        }
    }

    // find the directory to export the db to, based on the platform
    string fileName = "backup-" + DateTime.Now.ToString ("yyMMddThh:mm:ss") + ".db3";
    string directory = FileSystem.GetDirectoryForDatabaseExport ();     // returns "/storage/emulated/0/Download"
    string fullPath = Path.Combine (directory, fileName);

    // copy db to the directory
    bool copied = false;
    if (directory != null)
        copied = DBConnection.CopyDatabase (fullPath);

    if (copied)
        // alert user
    else
        // alert user
}
// ....

/* file 2: */
class DBConnection 
{
    private readonly string dbPath;

    // ....

    public bool CopyDatabase(string path) 
    {
        byte[] dbFile = File.ReadAllBytes(dbPath);
        File.WriteAllBytes(path, dbFile);        // --> this is where the exception is thrown <--

        return true;
    }

    // ....
}

So the question stands: how does one write a new file to the public external storage of an Android device with an API level of 29 or higher?


All the resources I have found so far, maybe you can gather more information than I did:

Answers

  • LeonLuLeonLu Member, Xamarin Team Xamurai
    edited October 2

    You can copy your DB to the public root directly . I used following way to copy the sqlite db file in Android 10. I store the DB file in pdatabase = new PrijemDatabase(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Notes3.db3"));.

    using Android.Widget;
    using Java.IO;
    using Notes.Droid;
    using Xamarin.Forms;
    
    
    [assembly: Dependency(typeof(GetDBFile1))]
    namespace Notes.Droid
    {
        class GetDBFile1 : CopyDB
        {
            public void copy()
            {
    
    
                string path = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "Notes3.db3");
                //    string path1 = "/data/user/0/com.companyname.Notes/files/Notes3.db";
                File f = new File(path);
    
    
                FileInputStream fis = null;
                FileOutputStream fos = null;
    
                fis = new FileInputStream(f);
                fos = new FileOutputStream("/mnt/sdcard/db_dump.db");
                while (true)
                {
                    int i = fis.Read();
                    if (i != -1)
                    { fos.Write(i); }
                    else
                    { break; }
                }
                fos.Flush();
    
                Toast.MakeText(Android.App.Application.Context, "DB dump OK", ToastLength.Short).Show();
    
    
                fos.Close();
                fis.Close();
            }
    
    
    
        }
    }
    

    Here is running GIF.

  • EitanEEitanE Member ✭✭
    edited October 2

    @LeonLu said:
    You can copy your DB to the public root directly . I used following way to copy the sqlite db file in Android 10. I store the DB file in pdatabase = new PrijemDatabase(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Notes3.db3"));.

        class GetDBFile1 : CopyDB
        {
            public void copy()
            {
                // ....
               
                fos = new FileOutputStream("/mnt/sdcard/db_dump.db");
                
                // ....
            }
        }
    

    Looks great, however, correct me if I'm wrong but the hard-coded path "/mnt/sdcard/" isn't very dependable - I can't make sure every phone has that directory. It's specific to the emulator. Is there another path, in which I can write into? One which is maybe returned with a function call?

  • LeonLuLeonLu Member, Xamarin Team Xamurai
    edited October 7

    @EitanE If you have some doubts about the /mnt/sdcard/db_dump.db, you can change the the path to(You will find this API has been deprecated:) to save the db file to the Download folder.

        string outPath =  Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath;
                fos = new FileOutputStream(outPath+ "/db_dump.db");
    

    In the end, please do not forget to the add android:requestLegacyExternalStorage="true" of the <application> tag in the manifest.xml file.

    And I suggest using GetExternalFilesDir to get the path to files folder inside Android/data/data/your_package/. in Android API level >=29

  • LeonLuLeonLu Member, Xamarin Team Xamurai

    @EitanE Are there any update for this issue?

  • EitanEEitanE Member ✭✭

    @LeonLu, I tried your solution, however when trying to write to /mnt/sdcard I got the error: System.UnauthorizedAccessException: Access to the path '/mnt/sdcard' is denied...

  • LeonLuLeonLu Member, Xamarin Team Xamurai

    Which kind of device did you test? Android emulator or android device?

    Did you add android:requestLegacyExternalStorage="true" of the <application> tag in the manifest.xml file?

    You can try to use string outPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath; fos = new FileOutputStream(outPath+ "/db_dump.db"); path, is that worked?

  • EitanEEitanE Member ✭✭

    @LeonLu, I'm using a physical Google Pixel 3a running Android 11.
    I have added the android:requestLegacyExternalStorage property to the <application> tag.
    I have tried using Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath; fos = new FileOutputStream(outPath+ "/db_dump.db");, which didn't work either.

  • LeonLuLeonLu Member, Xamarin Team Xamurai

    I test my code running in the Android 10, it worked as normal, But it not work in Android, then I found this google article.

    https://developer.android.com/about/versions/11/privacy/storage#file-directory-restrictions

    Apps that run on Android 11 but target Android 10 (API level 29) can still request the requestLegacyExternalStorage attribute. This flag allows apps to temporarily opt out of the changes associated with scoped storage, such as granting access to different directories and different types of media files. After you update your app to target Android 11, the system ignores the requestLegacyExternalStorage flag.

    See the topic Manage device storage

    Starting in Android 11, apps that use the scoped storage model can access only their own app-specific cache files. If your app needs to manage device storage

    So, we cannot write file to the public external storage.

Sign In or Register to comment.