Blog Infos
Author
Published
Topics
, , , ,
Published

Most apps store important user data locally in Room DB or any other local — but what if users switch phones or reinstall your app?
In this guide, you’ll learn how to add a Google Drive–based backup and restore feature to your Android app — using the Drive appDataFolder, so the backup stays private to your app in 10 simple steps.

We’ll use:

  • Google Sign-In for authentication
  • Google Drive REST API (v3) for upload/download
  • Coroutines + ViewModel for clean background operations
🧩 What We’ll Cover
  • Quick setup on the Google Cloud Console.
  • Building a GoogleDriveService to handle all API interactions.
  • Connecting the service to your UI using ViewModels.
  • Handling the core backup and restore logic.
⚙️ Step 1: Quick Google Cloud Setup (2 minutes)
  1. Go to Google Cloud Console.
  2. Create a new project or select an existing one.
  3. Enable the Google Drive API from the API Library.
  4. Go to Credentials → Create Credentials → OAuth client ID → Android.
  • Enter your app’s package name, e.g. com.example.app.
  • Enter your SHA-1 fingerprint (you can get it from Android Studio or using keytool).
  • Done ✅ — this enables your app to use Drive’s appDataFolder scope via Google Sign-In.
📦 Step 2: Add Required Dependencies

In your app/build.gradle file:

dependencies {
// Google Sign-In
implementation 'com.google.android.gms:play-services-auth:20.7.0'

// Google API Client for Drive
implementation 'com.google.api-client:google-api-client-android:2.2.0'
implementation 'com.google.apis:google-api-services-drive:v3-rev20220815-2.0.0'
implementation('com.google.api-client:google-api-client-gson:2.2.0') {
exclude group: 'org.apache.httpcomponents'
}
}

 

Add permission in AndroidManifest.xml:

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

 

🔐 Step 3: Google Drive Setup

To keep our code clean, we’ll create a dedicated service class to handle all Google Drive API calls.
First, we need a way to create an authorized Drive service object. This function uses the user’s signed-in account to create credentials scoped specifically to the appDataFolder.

class GoogleDriveService(private val context: Context) {

private fun getDriveService(account: GoogleSignInAccount): Drive {
val credential = GoogleAccountCredential.usingOAuth2(
context, Collections.singleton(DriveScopes.DRIVE_APPDATA)
).setSelectedAccount(account.account)

return Drive.Builder(
AndroidHttp.newCompatibleTransport(),
GsonFactory.getDefaultInstance(),
credential)
.setApplicationName("YourAppName") // Replace with your app name
.build()
}
}

 

🧠 Step 4: Handling User Sign-In

To perform any action, the user must first sign in. This function creates the Intent that launches the standard Google Sign-In flow.

 fun getSignInIntent(): Intent {
    val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestEmail()
        .requestScopes(Scope(DriveScopes.DRIVE_APPDATA)) // Request access to the AppData folder
        .build()

    val client = GoogleSignIn.getClient(context, gso)
    return client.signInIntent
}

This initializes the Drive client authenticated with the signed-in user.

Permission prompt for user

💾 Step 5: Upload the backup of Local Database

This is the core backup function. It takes the local database file, creates metadata for it, and uploads it to the appDataFolder. To avoid clutter, we first delete any old backup that might exist.

 suspend fun uploadBackup(account: GoogleSignInAccount, databasePath: String): File? {
    val driveService = getDriveService(account)
    val dbFile = java.io.File(databasePath)

    if (!dbFile.exists() || !dbFile.canRead() || dbFile.length() == 0L) {
        throw Exception("Database file is invalid.")
    }

    // Delete the previous backup file, if it exists.
    getLatestBackup(account)?.id?.let { fileId ->
        driveService.files().delete(fileId).execute()
    }

    val fileMetadata = File().apply {
        name = "your_app_database_backup_${System.currentTimeMillis()}.db"
        parents = listOf("appDataFolder") // This is key!
    }

    val mediaContent = com.google.api.client.http.FileContent("application/octet-stream", dbFile)

    return driveService.files().create(fileMetadata, mediaContent)
        .setFields("id, name, size, modifiedTime")
        .execute()
}

 

This uploads your app’s .db file to Drive’s private folder.

📂 Step 6: Finding and Restoring a Backup

To restore, we first need to find the backup file. This function queries the appDataFolder for the most recent file.

suspend fun getLatestBackup(context: Context, account: GoogleSignInAccount): File? {
    val drive = getDriveService(context, account)
    val files = drive.files().list()
        .setSpaces("appDataFolder")
        .setFields("files(id, name, modifiedTime, size)")
        .setPageSize(1)
        .execute()
suspend fun restoreBackup(account: GoogleSignInAccount, fileId: String, destinationPath: String) {
val driveService = getDriveService(account)
val destinationFile = java.io.File(destinationPath)
val tempBackupPath = "$destinationPath.tmp"

if (destinationFile.exists()) {
destinationFile.copyTo(java.io.File(tempBackupPath), overwrite = true)
}

try {
FileOutputStream(destinationFile).use { outputStream ->
driveService.files().get(fileId).executeMediaAndDownloadTo(outputStream)
}
java.io.File(tempBackupPath).delete() // Success, so delete the temp copy
} catch (e: Exception) {
// If restore fails, copy the temporary backup back
val tempBackupFile = java.io.File(tempBackupPath)
if (tempBackupFile.exists()) {
tempBackupFile.copyTo(destinationFile, overwrite = true)
tempBackupFile.delete()
}
throw Exception("Database restore failed: ${e.message}")
}
}

 

♻️ Step 7: Restore Backup File

Once we have the file ID, this function downloads it and replaces the local database. As a safety measure, it first creates a temporary local copy of the current database, which is restored if the download fails.

suspend fun restoreBackup(account: GoogleSignInAccount, fileId: String, destinationPath: String) {
val driveService = getDriveService(account)
val destinationFile = java.io.File(destinationPath)
val tempBackupPath = "$destinationPath.tmp"

if (destinationFile.exists()) {
destinationFile.copyTo(java.io.File(tempBackupPath), overwrite = true)
}

try {
FileOutputStream(destinationFile).use { outputStream ->
driveService.files().get(fileId).executeMediaAndDownloadTo(outputStream)
}
java.io.File(tempBackupPath).delete() // Success, so delete the temp copy
} catch (e: Exception) {
// If restore fails, copy the temporary backup back
val tempBackupFile = java.io.File(tempBackupPath)
if (tempBackupFile.exists()) {
tempBackupFile.copyTo(destinationFile, overwrite = true)
tempBackupFile.delete()
}
throw Exception("Database restore failed: ${e.message}")
}
}

 

🧭 Step 8: Connecting to the UI with ViewModel

In your ViewModel, you’ll use the GoogleDriveService to handle the logic triggered by user actions.

The Sign-In Flow

Your Activity or Fragment will launch the sign-in intent from the ViewModel and return the result.

// In your ViewModel
fun initiateGoogleSignIn() {
// Expose this intent to your UI to be launched by an ActivityResultLauncher
_uiState.update {
it.copy(signInIntent = driveService.getSignInIntent())
}
}

fun handleSignInResult(task: Task<GoogleSignInAccount>) {
try {
val account = task.getResult(ApiException::class.java)
// Sign-in success, update UI with account info
} catch (e: ApiException) {
// Sign-in failed
}
}

 

🧩 Step 9: Performing the backup

This is the most critical part. To safely back up a Room database, you must close the database connection before accessing the file on disk.

After the backup is complete, restarting the app is the simplest way to ensure the database connection is cleanly re-established.

// In your BackupViewModel
private fun performBackup() {
    viewModelScope.launch {
        _uiState.update { it.copy(isBackingUp = true) }
        try {
            val account = _uiState.value.account ?: throw Exception("No account found")
            val dbPath = getApplication<Application>().getDatabasePath("your_app_database.db").absolutePath

            withContext(Dispatchers.IO) {
                // IMPORTANT: Close the database before accessing the file
                database.close()
                kotlinx.coroutines.delay(500) // Brief delay to ensure file handle is released
                driveService.uploadBackup(account, dbPath)
            }
            
            _uiState.update { it.copy(snackbarMessage = "Backup successful! Restarting...") }
            restartApp() // Helper function to restart the application

        } catch (e: Exception) {
            _uiState.update { it.copy(snackbarMessage = "Backup failed: ${e.message}") }
        } finally {
            _uiState.update { it.copy(isBackingUp = false) }
        }
    }
}

 

🧑‍💻 Step 10: Triggering the Restore

The restore logic mirrors the backup process. We close the database, call the service to download and replace the file, and then restart the app to load the new data.

// In your BackupViewModel
fun startRestore() {
viewModelScope.launch {
_uiState.update { it.copy(isRestoring = true) }
try {
// ... get account and fileId from uiState ...
val destinationPath = getApplication<Application>().getDatabasePath("your_app_database.db").absolutePath

withContext(Dispatchers.IO) {
// IMPORTANT: Close the database before overwriting the file
database.close()
kotlinx.coroutines.delay(500)
driveService.restoreBackup(account, fileId, destinationPath)
}

_uiState.update { it.copy(snackbarMessage = "Restore successful! Restarting...") }
restartApp()

} catch(e: Exception) {
_uiState.update { it.copy(snackbarMessage = "Restore failed: ${e.message}") }
} finally {
_uiState.update { it.copy(isRestoring = false) }
}
}
}

 

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Kobweb:Creating websites in Kotlin leveraging Compose HTML

Kobweb is a Kotlin web framework that aims to make web development enjoyable by building on top of Compose HTML and drawing inspiration from Jetpack Compose.
Watch Video

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kobweb

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author o ...

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kob ...

Jobs

🧹 Key Notes & Gotchas
  • appDataFolder is private — users won’t see it in their Drive UI.
  • Always close your Room DB before copying.
  • Add both debug and release SHA-1 to your Cloud Console credentials.
  • Test on a real device with the same Google account you used for OAuth setup.
  • If restore fails, your service already keeps a .backup copy locally.
🏁 Final Thoughts

With under 200 lines of Kotlin, you now have a full Drive-based backup system — no external servers needed, no user file clutter, and full data privacy.

This approach works great for apps that rely on a single SQLite or Room database and want a “WhatsApp-like” backup experience.

 

This article was previously published on proandroiddev.com

Menu