Implementing Data Synchronization via Google Drive
Google Drive API allows mobile app to save user data in their personal cloud storage. Unlike Firebase or own backend, data is stored on user's own account — they control it and can delete anytime. Popular approach for apps with notes, documents, data for cross-device transfer.
App Data folder vs Drive Files
Google Drive API provides two storage types for apps:
Application Data folder — hidden folder, visible only to your app. User doesn't see files in Drive UI, but they consume their quota. Ideal for backups and settings sync.
Drive Files — normal Drive files, visible in user interface. Need drive.file scope — app can only work with files it created itself.
For app data sync — Application Data folder. For user documents — Drive Files.
Authentication via Google Sign-In
// Android: Google Sign-In setup with Drive scope
val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestScopes(Scope(DriveScopes.DRIVE_APPDATA))
.requestEmail()
.build()
val googleSignInClient = GoogleSignIn.getClient(activity, signInOptions)
// After successful login get credentials
fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
val account = completedTask.getResult(ApiException::class.java)
val credential = GoogleAccountCredential.usingOAuth2(
context, listOf(DriveScopes.DRIVE_APPDATA)
)
credential.selectedAccount = account.account
// Drive service for API requests
driveService = Drive.Builder(
NetHttpTransport(),
GsonFactory.getDefaultInstance(),
credential
).setApplicationName("MyApp").build()
}
On iOS — GoogleSignIn-iOS SDK with similar scope setup.
Creating and Updating Backup Files
class GoogleDriveBackupManager(private val driveService: Drive) {
suspend fun saveBackup(data: AppBackupData) = withContext(Dispatchers.IO) {
val json = Json.encodeToString(data)
val content = ByteArrayContent("application/json", json.toByteArray(Charsets.UTF_8))
// Find existing backup file
val existingFileId = findBackupFile()
if (existingFileId != null) {
// Update existing
driveService.files().update(existingFileId, null, content)
.execute()
} else {
// Create new in appDataFolder
val fileMetadata = File().apply {
name = "app_backup.json"
parents = listOf("appDataFolder")
}
driveService.files().create(fileMetadata, content)
.setFields("id, name, modifiedTime")
.execute()
}
}
private fun findBackupFile(): String? {
val result = driveService.files().list()
.setSpaces("appDataFolder")
.setFields("files(id, name, modifiedTime)")
.setQ("name = 'app_backup.json'")
.execute()
return result.files?.firstOrNull()?.id
}
suspend fun loadBackup(): AppBackupData? = withContext(Dispatchers.IO) {
val fileId = findBackupFile() ?: return@withContext null
val outputStream = ByteArrayOutputStream()
driveService.files().get(fileId)
.executeMediaAndDownloadTo(outputStream)
val json = outputStream.toString(Charsets.UTF_8.name())
Json.decodeFromString<AppBackupData>(json)
}
}
Syncing Multiple Files
For apps with many entities — separate file per data type or versioned snapshots. Convenient to compare file modifiedTime when determining which device last modified data:
data class DriveFileInfo(
val id: String,
val name: String,
val modifiedTime: com.google.api.client.util.DateTime,
val size: Long
)
suspend fun listBackupFiles(): List<DriveFileInfo> = withContext(Dispatchers.IO) {
val result = driveService.files().list()
.setSpaces("appDataFolder")
.setFields("files(id, name, modifiedTime, size)")
.setOrderBy("modifiedTime desc")
.execute()
result.files?.map { file ->
DriveFileInfo(
id = file.id,
name = file.name,
modifiedTime = file.modifiedTime,
size = file.getSize() ?: 0L
)
} ?: emptyList()
}
Background Sync
WorkManager for periodic background sync:
class DriveBackupWorker(
context: Context,
params: WorkerParameters,
private val backupManager: GoogleDriveBackupManager,
private val localDataManager: LocalDataManager
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
val localData = localDataManager.exportAll()
backupManager.saveBackup(localData)
Result.success()
} catch (e: UserRecoverableAuthIOException) {
// Token expired — need re-authentication
notifyAuthRequired()
Result.failure()
} catch (e: IOException) {
// Network error — retry
Result.retry()
}
}
}
// Register periodic backup
val backupRequest = PeriodicWorkRequestBuilder<DriveBackupWorker>(6, TimeUnit.HOURS)
.setConstraints(Constraints(
requiredNetworkType = NetworkType.UNMETERED, // WiFi only
requiresBatteryNotLow = true
))
.build()
Quota and Error Handling
Google Drive user quota usually 15 GB, but not infinite. Practices:
- Compress data before saving (gzip JSON files gives 60-80% savings)
- Keep only last N backup versions
- Show user used space
suspend fun getAppDataFolderSize(): Long = withContext(Dispatchers.IO) {
val files = listBackupFiles()
files.sumOf { it.size }
}
UserRecoverableAuthIOException — token expired or user revoked access. Can't ignore. Catch, show UI for re-authentication.
Rate limiting. Drive API limited to 1000 requests per 100 seconds per user. Batch requests, client caching, don't sync on every change — by timer or on app exit.
Implementing sync via Google Drive with background backup, versioning and auth handling: 2–3 weeks. Cost calculated individually.







