In-app updates on Android

How to show app updates to the user inside an Android app

Aditi Katiyar
6 min readDec 29, 2022
Photo by Miguel Á. Padriñán: https://www.pexels.com/photo/close-up-shot-of-keys-on-a-red-surface-2882668/

Most of the users enable background updates for their apps when the device is connected to an unmetered connection. But if the app has not been updated and we want to prompt the user to update the app, Google provides its Play Core library to do so.

The in-app updates allow the user to update an app from within the app itself rather than going onto Playstore.

Types of updates

There are 2 types of updates — Flexible(the user can deny this type of update) & Immediate(the user has to update the app forcibly, or else won’t be able to use the app).

Which type of update to show to the user depends on us. There can be multiple ways to do so. One intuitive idea is to check the current version of the app and show immediate updates to users who are on versions lower than x and flexible updates to users who are on versions above x, where x is the ‘minimum supported version’. You can build your own logic.

How to know whether an update is available on Playstore?

The Play Core library handles the in-app updates for us. Add the following dependency in your app’s build.gradle file.

dependencies {
implementation 'com.google.android.play:app-update:2.0.1'
implementation 'com.google.android.play:app-update-ktx:2.0.1'
...
}

Now create an object of AppUpdateManager. This object will let you check for updates, and handle them:

val appUpdateManager = AppUpdateManagerFactory.create(context)

Now check for updates using this:

fun checkUpdate() {
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
// update is available
triggerAppUpdate(appUpdateInfo)
}
}

The Play Core library will internally check on Playstore whether an update is available or not. If yes, then you will receive appUpdateInfo in the success listener. Also note, that you won’t get any callback here if the device does not have Playstore on it.

Now once you know that an update is there, you need to decide whether to show a Flexible update or an Immediate update.

fun triggerAppUpdate(appUpdateInfo: AppUpdateInfo) {
// get the current app version from packageManager
val currentVersion = context.packageManager.getPackageInfo(context.packageName, 0).longVersionCode

// Let's say you maintain some minSupportedVersion on backend or Remote Config
val minSupportedVersion = repository.getMinSupportedVersion()

if (currentVersion < minSupportedVersion) {
showImmediateUpdate(appUpdateInfo)
} else {
showFlexibleUpdate(appUpdateInfo)
}
}

Immediate Update

If you want to block the user from using the app without an update, go for an Immediate update. Call startUpdateFlowForResult() function to trigger the update process. Do it in the following cases:

  • When an update is available. (UpdateAvailability.UPDATE_AVAILABLE state)
  • When an update was previously downloaded but not installed. So you should start the installation. (InstallStatus.DOWNLOADED state)
  • When the app returns to the foreground, resume the update. (check for UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS state)
fun showImmediateUpdate(appUpdateInfo: AppUpdateInfo) {
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE ||
appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS ||
appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED
) {
appUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// AppUpdateType will be IMMEDIATE
AppUpdateType.IMMEDIATE,
// The current activity making the update request.
activity,
// Include a request code to later monitor this update request
APP_UPDATE_REQUEST_CODE
)
}
}

This will open a blocking UI. This is like another activity opening above your app. This activity is a part of the Play Core library. If you dismiss the dialog, you won’t be able to go back to your app, rather it will close.

To receive updates on in-app update installation, you can get the result in onActivityResult() callback:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == APP_UPDATE_REQUEST_CODE) {
// Do on result
if (resultCode == Activity.RESULT_CANCELED) {
sendAnalytics("Update canclled by user")
}
. . .
}
}

Flexible Update

If it is not mandatory for the user to update the app, show a Flexible update. Here, you need to check if the install status is InstallStatus.DOWNLOADED, then simply complete the update. Otherwise, if an update is available or an update is in progress, start/resume it by calling the startUpdateFlowForResult().

fun showFlexibleUpdate(appUpdateInfo: AppUpdateInfo) {
if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
showInstallAlert(onOKClick = {
// call 'completeUpdate()'
appUpdateManager.completeUpdate()
})
} else if (
appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS ||
appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
) {
appUpdateManager.startUpdateFlowForResult(
// Pass the intent that is returned by 'getAppUpdateInfo()'.
appUpdateInfo,
// AppUpdateType will be FLEXIBLE
AppUpdateType.FLEXIBLE,
// The current activity making the update request.
activity,
// Include a request code to later monitor this update request
APP_UPDATE_REQUEST_CODE
)
}
}

The user is shown a non-sticky dialog asking to update the app. If the user clicks on “No Thanks”, the dialog dismisses and the user continues to use the app as before without any update. If the user clicks on “Update”, the update downloads in the background, and the user continues to use the app.

However, when the download is complete, we get a callback from the Play Core library. To listen to the install updates, we have to register InstallStateUpdatedListener on the appUpdateManager object. This will let you listen to all the states:

private val updateListener: InstallStateUpdatedListener = { state ->
when {
state.installStatus() == InstallStatus.DOWNLOADED -> {
/*
* When update has downloaded, ask user whether to install the update.
* May be show some custom dialog saying:
* "Update download is complete. Do you want to install?" with 2
* action buttomns - "Yes" & "No"
*/
}

state.installStatus() == InstallStatus.CANCELED -> {
/*
* User has cancelled
*/
}

state.installStatus() == InstallStatus.DOWNLOADING -> {
/*
* while update download is in progress,
* you can show some progress bar withint the app
*/
}
}
appUpdateManager.registerListener(updateListener)

.
.
.
/*
* unregister the listener when not needed
*/
appUpdateManager.unregisterListener(updateListener)

Once the installation starts, the Play Core library displays a full-screen sticky UI. During installation, the user cannot use the app. Once complete, the app is restarted by itself.

Points to remember

  • Always check for updates in onResume() of your activity. This way you make sure that you are not missing updates even during an app session.
  • unregister InstallStateUpdatedListener when not in use, like in onPause(), to avoid memory leaks.
  • In the case of flexible updates, do not spam your users with an update dialog every time they open your app. Instead, show the flexible update dialog once in a while. e.g. once a week or so. You can maintain the last timestamp when an update was shown in shared pref, and the next time you are about to show a flexible update, check the difference between the current time and the last timestamp.

How to test in-app updates?

Create 2 production builds(testing on debug builds will not work because we only upload production builds on PlayStore.), one with lower version code & other with higher version code.

Go to internal app sharing on Play console and click this link. It will open an internal app-sharing dashboard.

image source

Upload both builds here. First, install the lower version build on your device(it must have Play Store) using the link. Open the app and run. Then click the link for the higher-version build. Do not install it. Rather go to the app and you should see the update.

--

--