How many times have you had to send a hot fix to an application, wishing there was a way to force all your users to update? I’ve certainly been in that situation a few times, and it’s quite stressful when there is no force update mechanism in place.

Many of the projects I’ve worked on before had one of these mechanisms and the code used to be always the same. Usually, it would consist in sending our application versionCode to our backend, and check if that version was stull supported or not. Which means there was work to be done in both the cleint and the backend side… Until now! In-App Updates from the Play Core library is here to solve this problem, I’ll show you how we implemented it in Mixtiles, and explain how you can do it too.

Where do we come from?

This is how our old force update mechanism in Mixtiles worked. As described above, we’d send the app’s versionCode to our backend and check if the version was still supported. If necessary, we’d show this blocking UI that would prevent the user from continuing.

As you can imagine, this is not ideal. There’s no way for us to know if the update actually happened or not, or if it’s happening in the background. On that premise, let’s take a look at how the new In-App Updates API works.

Adding the dependency

At the time of writing this post, the latest available version for the play code library is 1.6.1

implementation 'com.google.android.play:core:1.6.1'

How to check if an update is available

There are a few classes involved in the process, AppUpdateManager being the most important, which manages operations for self-updating our app. Through it’s getAppUpdateInfo() method we will retrieve its UpdateAvailability which can be one of 4 different values:

DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS

UNKNOWN

UPDATE_AVAILABLE

UPDATE_NOT_AVAILABLE

val appUpdateManager = AppUpdateManagerFactory.create(this)

val appUpdateInfo = appUpdateManager.appUpdateInfo.await()

if (appUpdateInfo.updateAvailability() ==

UpdateAvailability.UPDATE_AVAILABLE) {

// There is an update available

}

Note: The appUpdateManager.appUpdateInfo.await() is a simple extension function to make it more readable in Kotlin.

suspend fun Task<AppUpdateInfo>.await(): AppUpdateInfo {

return suspendCoroutine { continuation ->

addOnCompleteListener { result ->

if (result.isSuccessful) {

continuation.resume(result.result)

} else {

continuation.resumeWithException(result.exception)

}

}

}

}

What are the options?

There are two different ways of updating our app. We can start a Flexible or an Immediate update flow. Flexible will allow the user to discard the update and continue using the application, but Immediate will show a blocking UI that will prompt the user to update.

appUpdateManager.startUpdateFlowForResult(

appUpdateInfo,

AppUpdateType.FLEXIBLE , // or AppUpdateType.IMMEDIATE

context,

REQUEST_CODE_UPDATE)

It’s convenient to also note that AppUpdateInfo will also tell us if an update can be done at this time or not. It will take into account conditions like network, battery, etc… and decide if are good to continue with an update.

appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE))

Flexible Update

Flexible Update dialog

The user here has the option to discard the update and continue using the application. Should the Update button be pressed, the DownloadManager will kick in and download the update in the background. In order to track the state of the update we have access to an InstallStateUpdateListener.

val appUpdateManager = AppUpdateManagerFactory.create(context) val listener = InstallStateUpdatedListener {

// it.installStatus() represents the update state

// it.installErrorCode() represents the error state

} updateManager.registerListener(listener) // make sure to remove the listener once the process is complete

updateManager.unregisterListener(listener)

Downloading update in the

Once the InstallStateUpdate has been DOWNLOADED , we need to trigger the completion of the update telling the AppUpdateManager that the download has completed.

appUpdateManager.completeUpdate()

This will behave differently if the app is in foreground or not. When this is called with the app in foreground, a fullscreen UI will be presented to the user and the app will restart to that the update can be installed.

Installing a downloaded update

When this is called from with our app in the background, the system won’t notify that the update has completed. However the next time the app is opened it will be already updated.

Immediate Update

Blocking UI from Immediate flow

As mentioned above, this is the blocking UI scenario. You should only follow this path if you really want to force the user to update. Since this is all happening in a different process, we need to check the update state when navigating back to our Activity

override fun onActivityResult(

requestCode: Int,

resultCode: Int,

data: Intent

) {

if (requestCode === REQUEST_CODE_UPDATE) {

if (requestCode == RESULT_OK) {



}

}

}

Contrary to the Flexible update, we don’t need to call appUpdateManager.completeUpdate() since it’s all handled by the system.

Note: There is something important to consider here, it’s possible that the user leaves the app and opens it again before the update has completed. We should resume the process if necessary.

override fun onResume() {

super.onResume()

appUpdateManager.appUpdateInfo

.addOnSuccessListener {

if (it.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {

// resume the update flow

appUpdateManager.startUpdateFlowForResult(

it,

IMMEDIATE,

this,

REQUEST_CODE_UPDATE)

}

}

}

Our implementation

We decided to go with a combination of both our old implementation and the new Play Core library features.

In order to decide if an app version should be supported or not, we can’t relay on the In-App Update library. There is no concept of minimum required version. However, we can still keep using what we had already implemented! The solution is to check with our backend if the current app version is still supported, and if it isn’t we’ll kick off the Play Store update flow.

We usually ship our updates using the Google Play Rollout feature, it’s possible that, even though we know there’s a new version available, such version is not yet available to a particular device. It’s important then to check if the update is acually available through Google Play.

Note: In-App Updates API looks at the Production, Alpha and Beta tracks in Google Play. You can’t rely on this API to update old Production versions to Internal Test updates (Thanks Ben Weiss for the clarification)

AppStateService is the service we implemented to call our backend.

val appState = appStateService.getAppState().await() if (appState.appUpdateRequired) {

val result = appUpdateManager.appUpdateInfo.await()

if (result.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && result.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {

// New update available and can be done Immediately

} else {

// We are expecting an update but Google Play doesn't

// give us one, we'll manually send the user to Play Store

// instead

}

} else {

// No Update required, let's not annoy the user any further

}

If the app version is older that the minimum required version, we’ll show the full screen UI that forces you to update.

As you can see, we decided not to use the Flexible update flow. It would be possible to use it if the app version was still supported but an update was avaialable. However, we decided not to go this route since we thought that a big dialog like the one shown at the beginning of the article is too intrusive.

A possible solution here would be to have an in-between UI state that will notify the user about the availability of an update. Something like a Snackbar, but then it would still force us to show the Play Store dialog. It would be great if we could trigger a background update without going trough the Google Play dialog, but for now it’s something we can’t do.

I hope this article has shown you how the new In-App Updates library works, and sparked your curiosity to implement it in your app. It’s certainly a huge improvement for us developers, but even more so for our users.

Thanks to Ben Weiss and Amrit Sanjeev for the feedback and comments!