I want to allow users of my Android app to export the SQLite database file for content they create. My current solution copies the file to private storage (/data/data/com.package.name/files/Content.db
), then creates a URI for this file and opens the Share dialog. This is working, allowing me to export the database file using Dropbox, for example. Here is the code I'm using, partially adapted from https://stackoverflow.com/a/2661882 -
private void exportContent() {
copyContentToPrivateStorage();
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("application/octet-stream");
Uri uri = new FileProvider().getDatabaseURI(this);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "Backup via:"));
}
private void copyContentToPrivateStorage() {
// From https://stackoverflow.com/a/2661882
try {
File data = Environment.getDataDirectory();
File sd = getFilesDir();
if (sd.canWrite()) {
String currentDBPath = "//data//com.package.name//databases//Content.db";
String backupDBPath = "Content.db";
File currentDB = new File(data, currentDBPath);
File backupDB = new File(sd, backupDBPath);
if (currentDB.exists()) {
FileChannel src = new FileInputStream(currentDB).getChannel();
FileChannel dst = new FileOutputStream(backupDB).getChannel();
dst.transferFrom(src, 0, src.size());
src.close();
dst.close();
}
}
} catch (Exception e) {
Toast.makeText(this, e.toString(), Toast.LENGTH_LONG).show();
}
}
public class FileProvider extends android.support.v4.content.FileProvider {
public Uri getDatabaseURI(Context c) {
File exportFile = new File(c.getFilesDir(), "Content.db");
Uri uri = getUriForFile(c, "com.package.name.fileprovider", exportFile);
c.grantUriPermission("*", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
return uri;
}
}
It seems like I should be able to directly create a URI from the existing database path, instead of doing an intermediate copy. Is there a way to do this?
I could keep doing the intermediate copy, but I believe it would be bad practice to leave the second copy of the database in the data directory longer than necessary. Is there a way to clean it up and delete it after the chosen app has finished using the URI to share the file?
I solved this on my own. I'm documenting it here, per Neil's request.
This is where I launch the export/backup from my activity:
public class MyActivity {
private void exportUserContent() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("application/octet-stream");
Uri uri = new FileProvider().getDatabaseURI(this);
intent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(intent, "Backup via:"));
}
}
The FileProvider:
public class FileProvider extends android.support.v4.content.FileProvider {
public Uri getDatabaseURI(Context c) {
// https://developer.android.com/reference/android/support/v4/content/FileProvider.html
// old approach that worked until 2020-ish
// File data = Environment.getDataDirectory();
// String dbName = "UserContent.db";
// String currentDBPath = "//data//com.url.myapp//databases//" + dbName;
// File exportFile = new File(data, currentDBPath);
File exportFile = c.getDatabasePath(dbName); // new approach
return getFileUri(c, exportFile);
}
public Uri getFileUri(Context c, File f){
return getUriForFile(c, "com.url.myapp.fileprovider", f);
}
}
Inside AndroidManifest.xml:
<manifest ...>
<application ...>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.url.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
Inside \app\src\main\res\xml\filepaths.xml (I think the first entry is the relevant one, but I'll include the whole file for completeness):
<paths>
<files-path
path="../databases/"
name="mydatabases"/>
<files-path
path=""
name="migrations"/>
<external-path
path=""
name="external"/>
</paths>