giacom0c
giacom0c

Reputation: 309

Xamarin.Forms how to backup a SQLite database in SD card

I have a SQLite database in my project and I'd like to add a backup function to save that db in the SD card. Is there any simple way to do this for both Android and IOS using Xamarin.Forms? I searched among the old (some of them are veeery old) questions but I couldn't find a clear answer.

Upvotes: 3

Views: 2383

Answers (1)

SushiHangover
SushiHangover

Reputation: 74174

iOS:

There is no "sdcard" on iOS, but you can copy (File.Copy) the db to the App's Documents directory so it is accessible via the iTunes app so you can copy to your PC/Mac:

Use this directory to store user-generated content. The contents of this directory can be made available to the user through file sharing; therefore, his directory should only contain files that you may wish to expose to the user. The contents of this directory are backed up by iTunes and iCloud.

So assuming you are using App Support directory for your DB, which you should be:

public string GetDBPath(string dbFileName)
{
    // If you are not using App Support directory for your DBs you are doing it wrong on iOS ;-)
    var supportDir = NSSearchPath.GetDirectories(NSSearchPathDirectory.ApplicationSupportDirectory, NSSearchPathDomain.User, true)[0];
    var dbDir = Path.Combine(supportDir, "database");
    if (!Directory.Exists(dbDir))
         Directory.CreateDirectory(dbDir);
    return Path.Combine(supportDir, dbDir, dbFileName);
}

Copying to your App's doc directory is as simple as:

public void CopyDBToDocs(string dbFileName)
{
    var docDir = NSSearchPath.GetDirectories(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomain.User)[0];
    File.Copy(GetDBPath(dbFileName), Path.Combine(docDir, dbFileName));
}

Android:

On Android, it can be really simple, or a pain, depending upon the API of the device and what your Android API target of the app that you are developing.

As simple as:

public void CopyDBToSdCard(string dbName)
{
    File.Copy(GetDBPath(dbName), Path.Combine(Android.OS.Environment.DirectoryDownloads, dbName);
}

GetDBPath in this case is actually using the "true" database directory which is the subdir from your app's sandboxed "FileDir" location:

public string GetDBPath(string dbFileName)
{
    // FYI: GetDatabasePath("") fails on some APIs &| devices &|emulators so hack it... (Some API 23 devices, but not API 21, 27, 28, ...)
    var dbPath = context.GetDatabasePath("ZZZ").AbsolutePath.Replace("/ZZZ", "");
    if (!Directory.Exists(dbPath))
        Directory.CreateDirectory(dbPath);
    return context.GetDatabasePath(dbFileName).AbsolutePath;
}

Now to actually perform that file copy to the "external" location from the app, there is the app's manifest permission that is required:

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

Also if your app has "targeted" and is running on API-23 (Android 6.0) or above, you need to have the user consent to that permission at runtime.

Using the Android.Support.Compat is the easiest way. Basically you need to check if the permission has already been granted, if not show the user why you need permission and then let the OS ask the user to accept/deny the request. You will then be notified on those perm results.

Note: There is the Forms' Perm plugin from JamesM for the non-bold coders ;-) PermissionsPlugin

void GetExternalPerms()
{
    const string writePermission = Manifest.Permission.WriteExternalStorage;
    const string readPermission = Manifest.Permission.ReadExternalStorage;
    string[] PermissionsLocation =
     {
        writePermission, readPermission
     };
    if ((ContextCompat.CheckSelfPermission(this, writePermission) == Permission.Granted) && (ContextCompat.CheckSelfPermission(this, readPermission) == Permission.Granted))
    {
        return;
    }
    if (ActivityCompat.ShouldShowRequestPermissionRationale(this, writePermission))
        // Displaying a dialog would make sense at this point...
    }
    ActivityCompat.RequestPermissions(this, PermissionsLocation, 999);
}

Then the results of the user accepting or denying that request will to return via OnRequestPermissionsResult:

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
    switch (requestCode)
    {
        case 999: //  This is the code that we supply via RequestPermissions
            if (grantResults[0] == Permission.Granted)
            {
                // you have permission, so defer to your DB copy routine now
            }
            break;
        default:
            break;
    }
}

Upvotes: 2

Related Questions