Налаштування In-App Update (примусове оновлення) у Android-додатку
Користувачі не оновлюються самі по собі. За даними Play Console, середній час між релізом та встановленням оновлення — близько трьох тижнів. Якщо у новій версії критичний фікс безпеки або зламане API — це три тижні з роботуючою дирою. In-App Update API вирішує цю проблему прямо з додатка, без надежи на свідомість користувача.
Два режими та коли вибирати кожен
Google Play In-App Update API, доступний через com.google.android.play:app-update-ktx, надає два сценарії.
Flexible update — фонова завантаження, користувач продовжує роботу. Підходить для feature релізів. Після завершення завантаження показується snackbar з кнопкою «Перезапустити». Якщо користувач його закрив — потрібно окремо стежити InstallStatus.DOWNLOADED та показувати повторний запит.
Immediate update — повноекранний блокуючий інтерфейс від Google Play. Додаток фактично недоступен до завершення оновлення. Використовуємо при критичних патчах: зміна схеми шифрування, обов'язкова міграція БД, припинення підтримки старого API бэкенду. Важливо: Immediate update не означає, що користувач точно оновится. Він може закрити додаток — onActivityResult повернути RESULT_CANCELED, та потрібно обробляти цю ветку коректно, інакше додаток буде повністю недоступен.
Що реально вызивает проблеми
Самая частая помилка — ініціалізувати AppUpdateManager без перевірки доступності оновлення. Метод appUpdateManager.appUpdateInfo повертає Task<AppUpdateInfo>, та його потрібно чекати асинхронно. Спроби вызвать startUpdateFlowForResult без готового AppUpdateInfo дають IllegalStateException у рантаймі.
Друга поширена помилка — не враховувати updateAvailability == UPDATE_NOT_AVAILABLE на сборках з Firebase App Distribution або при тестуванні через FakeAppUpdateManager. У продакшені цей шлях недосяжимий, але у CI падають тесты.
Третя проблема — версіонування. Порівняння йде по versionCode, не по versionName. Якщо у кількох флейворах одного додатка використовується один versionCode — оновлення не буде пропонуватися, навіть якщо бінарники різні.
Як ми реалізуємо
Інтеграція починається з аудиту поточної схеми версіонування — перевіряємо, що versionCode монотонно зростає та немає колізій між флейворами. Потім додаємо залежність та пишемо UpdateManager-клас у layer domain (чистая архітектура — UI не знає про Play Services напрямку).
val appUpdateManager = AppUpdateManagerFactory.create(context)
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
appUpdateInfoTask.addOnSuccessListener { info ->
if (info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)
) {
appUpdateManager.startUpdateFlowForResult(
info, AppUpdateType.FLEXIBLE, activity, REQUEST_CODE_UPDATE
)
}
}
Для Immediate-сценарія додаємо staleDays — примусовий апдейт включається лише якщо оновлення доступно більше N днів. Це знижує раздраження користувачів при дрібних патчах.
Обробка onResume критична: якщо користувач свернув додаток під час Flexible-завантаження та повернув — потрібно перевірити installStatus та запропонувати рестарт. Без цього додаток працює на старому коді, хоча новий уже лежит на диску.
Тестування
FakeAppUpdateManager з play-app-update дозволяє емулювати всі стани без публікації у Play Store. У Espresso-тестах можна прогнати повний цикл: доступність → початок завантаження → завершення → рестарт. Без цього покриття баги у update flow уходять у продакшен незмічені — сам Google Play показує UI поверх додатка, та стандартні UI-тесты його не видят.
Процес роботи
Спочатку аналізуємо поточну архітектуру додатка та схему версіонування. Визначаємо, які релізи повинні бути Immediate, а які Flexible — зазвичай це закріплюється у release checklist. Реалізуємо та покриваємо тестами. Фінальна перевірка — через Internal Testing track у Play Console з реальним пристроєм.
Строки — від двух до п'яти робочих днів залежно від складності архітектури додатка.







