4

Using the Google Drive API for Android and some answers on Stack Overflow, I have managed to implement Google Sign-In into my app, and backup the SQLite database stored on the user's device to Google Drive.

For reference, here is how I save the database to Google Drive (this is done in a final class called DriveDbHandler):

private static final String LOG_TAG = "DriveDbHandler";

private static final String PACKAGE_NAME = "com.package.example";

private static final String DATABASE_PATH =
        "/data/data/" + PACKAGE_NAME + "/databases/" + DbHelper.DATABASE_NAME;

private static final String FILE_NAME = DbHelper.DATABASE_NAME;
private static final String MIME_TYPE = "application/x-sqlite-3";

private DriveDbHandler() {
}


public static void tryCreatingDbOnDrive(final GoogleApiClient googleApiClient) {
    // We need to check if the database already exists on Google Drive. If so, we won't create
    // it again.

    Query query = new Query.Builder()
            .addFilter(Filters.and(
                    Filters.eq(SearchableField.TITLE, FILE_NAME),
                    Filters.eq(SearchableField.MIME_TYPE, MIME_TYPE)))
            .build();
    DriveFolder appFolder = Drive.DriveApi.getAppFolder(googleApiClient);

    appFolder.queryChildren(googleApiClient, query).setResultCallback(
            new ResultCallback<DriveApi.MetadataBufferResult>() {
                @Override
                public void onResult(@NonNull DriveApi.MetadataBufferResult metadataBufferResult) {
                    if (!metadataBufferResult.getStatus().isSuccess()) {
                        Log.e(LOG_TAG, "Query for " + FILE_NAME + " unsuccessful!");
                        return;
                    }

                    int count = metadataBufferResult.getMetadataBuffer().getCount();

                    Log.d(LOG_TAG, "Successfully ran query for " + FILE_NAME + " and found " +
                            count + " results");

                    if (count > 1) {
                        Log.e(LOG_TAG, "App folder contains more than one database file! " +
                                "Found " + count + " matching results.");
                        return;
                    }

                    // Create the database on Google Drive if it doesn't exist already
                    if (count == 0) {
                        Log.d(LOG_TAG, "No existing database found on Google Drive");
                        saveToDrive(googleApiClient);
                    }
                }
            });
}

private static void saveToDrive(final GoogleApiClient googleApiClient) {
    Log.d(LOG_TAG, "Starting to save to drive...");

    // Create content from file
    Drive.DriveApi.newDriveContents(googleApiClient).setResultCallback(
            new ResultCallback<DriveApi.DriveContentsResult>() {
                @Override
                public void onResult(@NonNull DriveApi.DriveContentsResult driveContentsResult) {
                    if (!driveContentsResult.getStatus().isSuccess()) {
                        Log.w(LOG_TAG, "Drive contents result not a success! " +
                                "Not saving data to drive.");
                        return;
                    }

                    Log.d(LOG_TAG, "Created drive contents for file");
                    createNewFile(googleApiClient, driveContentsResult.getDriveContents());
                }
            });
}

private static void createNewFile(GoogleApiClient googleApiClient, DriveContents driveContents) {
    // Write file to contents (see http://stackoverflow.com/a/33610727/4230345)
    File file = new File(DATABASE_PATH);
    OutputStream outputStream = driveContents.getOutputStream();
    try {
        InputStream inputStream = new FileInputStream(file);
        byte[] buf = new byte[4096];
        int c;
        while ((c = inputStream.read(buf, 0, buf.length)) > 0) {
            outputStream.write(buf, 0, c);
            outputStream.flush();
        }
        outputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    Log.d(LOG_TAG, "Written file to output stream of drive contents");

    // Create metadata
    MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder()
            .setTitle(FILE_NAME)
            .setMimeType(MIME_TYPE)
            .build();

    // Create the file on Google Drive
    DriveFolder folder = Drive.DriveApi.getAppFolder(googleApiClient);
    folder.createFile(googleApiClient, metadataChangeSet, driveContents).setResultCallback(
            new ResultCallback<DriveFolder.DriveFileResult>() {
        @Override
        public void onResult(@NonNull DriveFolder.DriveFileResult driveFileResult) {
            if (!driveFileResult.getStatus().isSuccess()) {
                Log.w(LOG_TAG, "File did not get created in Google Drive!");
                return;
            }

            Log.i(LOG_TAG, "Successfully created file in Google Drive");
        }
    });
}

So here's my issue:

I can save the database to Google Drive, but how do I update the Google Drive version with any changes made locally?

For example, I can delete 3 rows from table A and then add 5 rows to table B locally (to the device's SQLite database), but how do I update the Google Drive version with this change?

I have considered deleting the entire Drive file and re-uploading it, but this would result in a different DriveId for that file, to my understanding.

I'm wondering if I would be able to take advantage of the API's modification handling (explained here), where changes are queued for upload if the device has no internet connection.

Farbod Salamat-Zadeh
  • 19,687
  • 20
  • 75
  • 125
  • I don't think theres any easy/elegant solution to this problem when you are using the database as a file – Distjubo Jan 02 '17 at 15:54
  • There is a [tool](https://sqlite.org/sqldiff.html) though that can calculate the difference between 2 sqlite databases – Distjubo Jan 02 '17 at 15:55
  • @Distjubo Yeah, that's what I suspected. Do you have a solution (regardless of how elegant it is) that I may be able to use. How would I use the _sqldiff_ tool you suggested? – Farbod Salamat-Zadeh Jan 02 '17 at 15:55
  • sqldiff would only work if you had that online file present as a database where you can insert/update stuff into. But since you don't, you'll have to make a copy of the file the last time you send it to gdrive, calculate the difference and [patch](https://developers.google.com/drive/v2/reference/files/patch) it – Distjubo Jan 02 '17 at 15:56
  • @Distjubo To clarify, I'm using the [Android API](https://developers.google.com/drive/android/intro) not the REST API. How would I modify the Google Drive version of the database (using the Android API)? – Farbod Salamat-Zadeh Jan 02 '17 at 16:00

1 Answers1

3

According to this answer, the android API for Google Drive already handles the difference calculation for you. So there is no need to do anything complicated, just use the API as if you were completely rewriting that file.

You might also want to take advantage of the APIs transparent offline syncing functionality.

Community
  • 1
  • 1
Distjubo
  • 959
  • 6
  • 21
  • Thanks for the clarification - I'm glad there's nothing too tricky to do here ;) . When rewriting the file, do I need to do anything to remove the contents from that file, or do I just write to it and it will overwrite by default? – Farbod Salamat-Zadeh Jan 02 '17 at 16:19
  • Update - Should I just open the file in `WRITE_ONLY` mode, as outlined [here](https://developers.google.com/drive/android/) and [here](https://developers.google.com/drive/android/files#opening_the_file_contents_1)?. – Farbod Salamat-Zadeh Jan 02 '17 at 16:26
  • Thanks for your help - I'll try this out and let you know how I get on with it. – Farbod Salamat-Zadeh Jan 02 '17 at 16:46