Setting Up In-App Update (Forced Update) in Android Apps
Users don't update themselves. Per Play Console data, median time between release and update installation is about three weeks. If new version has critical security fix or broken API — that's three weeks with open vulnerability. In-App Update API solves this directly from the app without relying on user consciousness.
Two Modes and When to Choose Each
Google Play In-App Update API, available via com.google.android.play:app-update-ktx, provides two scenarios.
Flexible update — background download, user continues work. Suits feature releases. After download completes, snackbar appears with "Restart" button. If user dismisses it, need separate tracking of InstallStatus.DOWNLOADED and show repeated request.
Immediate update — full-screen blocking interface from Google Play. App is essentially unavailable until update completes. Use for critical patches: encryption schema change, mandatory DB migration, backend API version discontinuation. Important: Immediate update doesn't guarantee user updates. User can close app — onActivityResult returns RESULT_CANCELED, need to handle correctly, otherwise app becomes completely unavailable.
What Really Causes Problems
Most common mistake — initializing AppUpdateManager without availability check. Method appUpdateManager.appUpdateInfo returns Task<AppUpdateInfo>, must await asynchronously. Calling startUpdateFlowForResult without ready AppUpdateInfo throws IllegalStateException at runtime.
Second common bug — not accounting for updateAvailability == UPDATE_NOT_AVAILABLE on Firebase App Distribution builds or testing via FakeAppUpdateManager. In production unreachable, but CI tests fail.
Third problem — versioning. Comparison by versionCode, not versionName. If multiple flavors of one app use same versionCode — update won't be offered even if binaries differ.
How We Implement
Integration starts with auditing current versioning scheme — verify versionCode increases monotonically with no flavor collisions. Then add dependency and write UpdateManager class in domain layer (clean architecture — UI doesn't know Play Services directly).
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
)
}
}
For Immediate scenario add staleDays — forced update activates only if update available more than N days. Reduces user frustration with minor patches.
onResume handling critical: if user minimized app during Flexible download and returned — must check installStatus and offer restart. Without this, app runs old code though new already on disk.
Testing
FakeAppUpdateManager from play-app-update emulates all states without publishing to Play Store. In Espresso tests can run full cycle: availability → download start → completion → restart. Without coverage, update flow bugs reach production undetected — Google Play shows UI over app, standard UI tests don't see it.
Process
Analyze current architecture and versioning scheme. Determine which releases should be Immediate, which Flexible — usually fixed in release checklist. Implement and cover with tests. Final verification — Internal Testing track in Play Console on real device.
Timeline — 2 to 5 working days depending on app architecture complexity.







