Реалізація синхронізації даних через Google Drive
Google Drive API дозволяє мобільному додатку зберігати дані користувача у його особистому облічному сховищі. На відміну від Firebase або власного бекенда, дані зберігаються на аккаунті самого користувача — він контролює їх та може видалити у будь-який момент. Популярний підхід для додатків з нотатками, документами, даними для переносу між пристроями.
App Data folder vs Drive Files
Google Drive API надає два типи сховища для додатків:
Application Data folder — скрита папка, видима тільки вашому додатку. Користувач не бачить файли в Drive UI, але вони витрачають його квоту. Ідеально для резервних копій та синхронізації налаштувань.
Drive Files — звичайні файли в Drive користувача, видимі у інтерфейсі. Потрібен scope drive.file — додаток може працювати тільки з файлами, які сам створив.
Для синхронізації даних додатку — Application Data folder. Для роботи з користувальницькими документами — Drive Files.
Аутентифікація через Google Sign-In
// Android: налаштування Google Sign-In з Drive scope
val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestScopes(Scope(DriveScopes.DRIVE_APPDATA))
.requestEmail()
.build()
val googleSignInClient = GoogleSignIn.getClient(activity, signInOptions)
// Після успішного входу отримуємо 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 для API-запитів
driveService = Drive.Builder(
NetHttpTransport(),
GsonFactory.getDefaultInstance(),
credential
).setApplicationName("MyApp").build()
}
На iOS — GoogleSignIn-iOS SDK з аналогічною налаштуванням scope.
Створення та оновлення файлів резервної копії
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))
// Шукаємо існуючий файл резервної копії
val existingFileId = findBackupFile()
if (existingFileId != null) {
// Оновлюємо існуючий
driveService.files().update(existingFileId, null, content)
.execute()
} else {
// Створюємо новий у 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)
}
}
Синхронізація кількох файлів
Для додатків з множеством сутностей — окремий файл на кожен тип даних або версіоновані снапшоти. Зручно порівнювати modifiedTime файлів при визначенні, який пристрій останнім модифікував дані:
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()
}
Фонова синхронізація
WorkManager для періодичної синхронізації у фоне:
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) {
// Токен істік — потрібна переаутентифікація
notifyAuthRequired()
Result.failure()
} catch (e: IOException) {
// Сетева помилка — retry
Result.retry()
}
}
}
// Реєстрація періодичного бекапу
val backupRequest = PeriodicWorkRequestBuilder<DriveBackupWorker>(6, TimeUnit.HOURS)
.setConstraints(Constraints(
requiredNetworkType = NetworkType.UNMETERED, // тільки WiFi
requiresBatteryNotLow = true
))
.build()
Обробка квоти та помилок
Google Drive квота у користувача зазвичай 15 ГБ, але не нескінченна. Практики:
- Стиснути дані перед сохраненням (gzip JSON-файлів дає 60-80% економії)
- Зберігати тільки останні N версій резервних копій
- Показувати користувачу розмір займаного місця
suspend fun getAppDataFolderSize(): Long = withContext(Dispatchers.IO) {
val files = listBackupFiles()
files.sumOf { it.size }
}
UserRecoverableAuthIOException — токен істік або користувач відозвав доступ. Не можна ігнорувати. Перехватуємо, показуємо UI для повторної авторизації.
Rate limiting. Drive API ограничена 1000 запитів на 100 секунд на користувача. Батчеві запити, кеширування на клієнті, не синхронізувати при кожній зміні — по таймеру або при виході з додатку.
Реалізація синхронізації через Google Drive з фоновим бекапом, версіонуванням та обробкою авторизації: 2–3 тижні. Вартість рассчитывается індивідуально.







