Android Google Drive API Storing App Data (Like WhatsApp Google Drive Backup & Restore)

May 21, 2018
Create, Query and Read File In App Folder

Refer to Setup Android Google Drive Api for initial setup.

The following code backup a String to a file named backup-latest.db in Google Drive App Folder.

The App Folder is a special folder that is only accessible by your application. Its content is hidden from the user and from other apps. Despite being hidden from the user, the App Folder is stored on the user’s Drive and therefore uses the user’s Drive storage quota. The App Folder can be used to store configuration files, temporary files, or any other types of files that belong to the user but should not be tampered with.

  • call initBackup to sign in to Google Drive and perform backup to App Folder.
  • call initRestore to sign in to Google Drive and perform restore from App Folder.
  • backupToGoogleDrive create a file in App Folder
  • restoreFromGoogleDrive perform a search/query for backup file in App Folder followed by read operation.
class BackupActivity : AppCompatActivity() {

    companion object {
        private const val GOOGLE_SIGNIN_BACKUP_REQUEST_CODE = 1
        private const val GOOGLE_SIGNIN_RESTORE_REQUEST_CODE = 2
    }

    private lateinit var googleSignInClient: GoogleSignInClient
    private var driveClient: DriveClient? = null
    private var driveResourceClient: DriveResourceClient? = null

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == GOOGLE_SIGNIN_BACKUP_REQUEST_CODE || requestCode == GOOGLE_SIGNIN_RESTORE_REQUEST_CODE) {
            if (resultCode == Activity.RESULT_OK) {
                val googleSignInAccount = GoogleSignIn.getLastSignedInAccount(this)
                driveClient = googleSignInAccount?.let { Drive.getDriveClient(this, it) }
                driveResourceClient = googleSignInAccount?.let { Drive.getDriveResourceClient(this, it) }

                if (requestCode == GOOGLE_SIGNIN_BACKUP_REQUEST_CODE) {
                    backupToGoogleDrive()
                }
                else if (requestCode == GOOGLE_SIGNIN_RESTORE_REQUEST_CODE) {
                    restoreFromGoogleDrive()
                }
            }
            else {

            }  
        }      
    }

    private fun initBackup() {
        googleSignInClient = buildGoogleSignInClient()
        startActivityForResult(googleSignInClient.signInIntent, GOOGLE_SIGNIN_BACKUP_REQUEST_CODE)
    }

    private fun initRestore() {
        googleSignInClient = buildGoogleSignInClient()
        startActivityForResult(googleSignInClient.signInIntent, GOOGLE_SIGNIN_RESTORE_REQUEST_CODE)
    }

    private fun signOut() {
        googleSignInClient.signOut()
    }

    private fun buildGoogleSignInClient(): GoogleSignInClient {
        val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
              // .requestScopes(Drive.SCOPE_FILE)
              .requestScopes(Drive.SCOPE_APPFOLDER)
              .build()
        return GoogleSignIn.getClient(this, signInOptions)
    }

    private fun backupToGoogleDrive(backupData: String) {
        driveResourceClient?.let { driveResourceClient ->

            val appFolderTask = driveResourceClient.appFolder
            val createContentsTask = driveResourceClient.createContents()
            val task = Tasks.whenAll(appFolderTask, createContentsTask)
                    .continueWithTask({ task ->
                        val parent = appFolderTask.result
                        val contents = createContentsTask.result
                        val outputStream = contents.outputStream

                        val input = backupData
                        val inputStream = ByteArrayInputStream(input.toByteArray(Charsets.UTF_8))

                        inputStream.use { input ->
                            outputStream.use { output ->
                                input.copyTo(output)
                            }
                        }

                        val changeSet = MetadataChangeSet.Builder()
                                .setTitle("backup-latest.db")
                                .setMimeType("application/octet-stream")
                                .setStarred(true)
                                .build()

                        driveResourceClient.createFile(parent, changeSet, contents)

                    })

            task.addOnSuccessListener { driveFile ->
                        // show success message
                    }
                    .addOnFailureListener { e ->
                        Log.e(TAG, "GoogleDrive create file failed", e)

                        // show failure message
                    }
        }
    }

    private fun restoreFromGoogleDrive() {
        driveResourceClient?.let { driveResourceClient ->
            val appFolderTask = driveResourceClient.appFolder

            appFolderTask.continueWithTask { task ->
                val parent = task.result
                val query = Query.Builder()
                        .addFilter(Filters.eq(SearchableField.TITLE, "backup-latest.db"))
                        .build()
                driveResourceClient.queryChildren(parent, query)
            }.addOnSuccessListener { meta ->
                if (meta.count > 0) {
                    val fileMeta = meta.get(0)
                }
                else {
                    // viewModel.toastMessage.value = R.string.message_no_backup_found
                }
            }.continueWithTask { task ->
                val meta = task.result
                if (meta.count == 0)   {
                    return@continueWithTask null
                }

                val fileMeta = meta.get(0)
                val file = fileMeta.driveId.asDriveFile()
                driveResourceClient.openFile(file, DriveFile.MODE_READ_ONLY)
            }.addOnSuccessListener { driveContents ->
                val tempInputStream = driveContents.inputStream
                val inputStream = tempInputStream.copyToByteArrayStream()

                val outputStream = ByteArrayOutputStream()

                inputStream.use { input ->
                    outputStream.use { output ->
                        input.copyTo(output)
                    }
                }

                val restoreData = String(outputStream.toByteArray(), Charsets.UTF_8)

                // use this to free resources?
                driveResourceClient.discardContents(driveContents)
            }
        }
    }    
}

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.