Реалізація синхронізації даних через 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. Отримайте консультацію — оцінимо ваш проєкт безкоштовно. Зв'яжіться з нами для детального аналізу вашого застосунку.







