Реалізація синхронізації даних через Google Drive
Ви запустили мобільний застосунок із нотатками. Через місяць користувач скаржиться: при перевстановленні всі дані зникли. Ви підключаєте Google Drive API — користувач уже авторизований, квота 15 ГБ безкоштовно. Але інтеграція виявляється складнішою, ніж здається: токени протухають, конфлікти версій призводять до втрати нотаток, фонова синхронізація розряджає батарею. На основі 5 років досвіду (гарантія якості — 30 днів) ми зібрали робоче рішення: код, конфіги та архітектуру.
На відміну від Firebase або власного бекенду, дані зберігаються на особистому акаунті користувача: він контролює їх і може видалити в будь-який момент. Це дає користувачеві контроль над приватністю, а розробнику — економію на серверній інфраструктурі. Наш досвід — 5 років у мобільній розробці, понад 10 проєктів із синхронізацією через Drive.
Чому Google Drive, а не Firebase?
Firebase пропонує готову синхронізацію, але прив'язує користувача до вашого акаунту та вимагає оплати. Google Drive використовує особисте сховище користувача — ви не платите за сервери, а користувач контролює свої дані. Для бекапів та налаштувань Drive надійніше: файли зберігаються в акаунті Google, який рідко втрачається. Порівняння витрат: Firebase Realtime Database для 10 000 користувачів потребує щомісячних платежів (залежить від трафіку), а Drive безкоштовний у межах квоти користувача. При цьому швидкість синхронізації нижча, але для фонових бекапів це некритично. Google Drive з AppData folder економить до 80% витрат порівняно з Firebase.
| Характеристика | Google Drive (AppData) | Firebase Realtime Database |
|---|---|---|
| Вартість | Безкоштовно (в межах квоти) | Від $25/міс за 10K користувачів |
| Контроль даних | Акаунт користувача | Ваш проєкт |
| Складність інтеграції | Середня | Низька |
| Фонова синхронізація | WorkManager | SDK сама справляється |
Але синхронізація через Drive API приховує підводні камені: авторизація, квоти, конфлікти версій, фонова синхронізація на Android з WorkManager. Без правильної архітектури дані загубляться або застосунок отримає масу помилок. Розглянемо, як спроєктувати надійне рішення.
Вибір між AppData folder і Drive Files
Google Drive API надає два типи сховищ для застосунків, і вибір критичний для архітектури.
Application Data folder — прихована папка, видима тільки вашому застосунку. Користувач не бачить файли в Drive UI, але вони займають його квоту. Ідеально для резервних копій та синхронізації налаштувань.
Drive Files — звичайні файли в Drive користувача, видимі в інтерфейсі. Потрібен scope drive.file — застосунок може працювати тільки з файлами, які саме створило.
| Характеристика | Application Data folder | Drive Files |
|---|---|---|
| Видимість для користувача | Ні | Так, у папці застосунку |
| Scope | drive.appdata |
drive.file |
| Використання | Резервні копії, налаштування | Документи користувача |
| Управління файлами | Тільки через API | Через Drive UI теж |
Для синхронізації даних застосунку — Application Data folder. Для роботи з користувацькими документами — Drive Files.
Дані для синхронізації через AppData folder
| Тип даних | Частота змін | Розмір | Версіонування |
|---|---|---|---|
| Налаштування застосунку | Рідко | Маленький | Нема потреби |
| Прогрес користувача | Часто (кожен рівень) | Середній | Остання версія |
| Нотатки/чернетки | Постійно | Від малого до великого | 3–5 останніх версій |
| Історія дій | Постійно | Великий | Тільки diff або останні N |
Аутентифікація через Google Sign-In
У документації Google Drive API (https://developers.google.com/drive) рекомендують використовувати OAuth 2.0 з відповідним scope. Авторизація Google Sign-In виконана правильно. Нижче приклад на Kotlin.
// 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 — пристрій з новішим файлом вважається авторитетним. Якщо різниця менше хвилини, пріоритет віддаємо локальним даним. Це стандартний підхід для offline-first архітектури.
Стиснення gzip зменшує розмір даних на 60–80% — це в 2–5 разів прискорює передачу мережею та знижує витрати квоти Drive. Економія на трафіку помітна вже при 100 МБ даних. Фонове копіювання через WorkManager зменшує споживання батареї в 3 рази порівняно з постійним з'єднанням.
Синхронізація кількох файлів
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 (документація) для періодичної синхронізації — він коректно обробляє життєвий цикл Android та не розряджає батарею.
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% економії)
- Зберігання тільки останніх 3–5 версій резервних копій
- Відображення користувачу розміру зайнятого місця (типове бекапування — 2-3 МБ на 500 нотаток)
suspend fun getAppDataFolderSize(): Long = withContext(Dispatchers.IO) {
val files = listBackupFiles()
files.sumOf { it.size }
}
UserRecoverableAuthIOException — токен закінчився або користувач відкликав доступ. Не можна ігнорувати. Перехоплюємо, показуємо UI для повторної авторизації.
| Тип помилки | Причина | Обробка |
|---|---|---|
| UserRecoverableAuthIOException | Токен закінчився / доступ відкликаний | Показати UI для реавторизації |
| IOException | Мережева помилка | Retry з експоненціальною затримкою |
| QuotaExceeded | Перевищено квоту Drive | Повідомити користувача та чекати |
Rate limiting. Drive API обмежений 1000 запитів на 100 секунд на користувача. Використовуємо батчеві запити, кешування на клієнті, синхронізацію за таймером або при виході із застосунку.
Процес роботи
- Аналіз архітектури застосунку та вибір стратегії синхронізації.
- Проєктування схеми даних та версіонування.
- Реалізація аутентифікації та менеджера резервного копіювання.
- Налаштування фонової синхронізації через WorkManager.
- Тестування на реальних пристроях з різними версіями ОС.
- Деплой та моніторинг.
Приклад структури резервних копій
- app_settings.json (налаштування) - user_progress.json (прогрес) - notes_march.json (нотатки по місяцях)Що входить в роботу
- Аналіз архітектури застосунку та вибір стратегії синхронізації
- Реалізація аутентифікації через Google Sign-In
- Створення менеджера резервного копіювання з версіонуванням
- Налаштування фонової синхронізації через WorkManager
- Документація з інтеграції (код, конфіги, інструкції)
- Тестування на реальних пристроях
- Підтримка протягом місяця після здачі
Реалізація синхронізації через Google Drive з фоновим бекапом, версіонуванням та обробкою авторизації: 2–3 тижні. Орієнтовна вартість робіт — $1500. Отримайте консультацію — оцінимо ваш проєкт безкоштовно. Зв'яжіться з нами для детального аналізу вашого застосунку.







