In App Updates in Android
What is In App Updates ?
As we all know android devices are mostly used devices in the world, around 1.3 billion people are using android. Once an application is downloaded by a user from playstore then to notify users about any latest update of the app was a bit tedious as play core library did not expose any api for this but in google i/o 2019 google has lived this api to support in app updates so that while user is using app then he/she does not have to go to playstore to update.
Link play core library : https://developer.android.com/guide/app-bundle/playcore#include_playcore
GitHub Link for code Sample: https://github.com/rahul2689/AppUpdatesSample/tree/masterlocal
Why there is a need of in app updates?
When an app is upgraded on playstore then those users who are already using the app do not get any notification that a new version of the same app is available on play store, due to this users who does not have chosen auto update option would not get the new version of the app until they explicitly goto playstore to upgrade the app.
As per the data analysis on multiple apps I found that it takes around 2-3 weeks to migrate around 70% people from old version to updated version and even after that 25-30% users use the older versions of the app.
After making in app update api live this problem got solved because every user will get a prompt to update to new version of the app.
Types of updates:
1.Flexible Update :
- Flexible update is basically a non critical update it means it is more like a soft update in which user might or might not choose for update to try out some new features.
- In flexible update, process to update(download and install) to new app version done in background after user choose(click on the update button showing in dialog) to update on a system dialog showing in the figure below.
- Once the new version of the app has been successfully downloaded in background then system fires a callback to inform about install status then we can show a custom snack bar with reload button to restart the app and install new version successfully in foreground.
Figure 1: Flexible Update Flow
2.Immediate Update
- Immediate update is a critical update it means users have to update the app before using it further.
- In Immediate update, full screen ui is shown to update to new version.
- In Immediate update user cannot using existing app without
- In Immediate update app updation is done in foreground and after complete updation app will restart automatically.
- App update and restart is handled by google play itself.
When to use Flexible Update Type:
When any new feature is added but it is not that much critical for app then best suited type of update is Flexible Type.
For example : When some new features are added in new version of the app then it is upto users that they want to move to a new version of the app to use those or not it means Google play can’t force users to upgrade the app in this type of update.
When to use Immediate Update Type:
- When any critical thing is fixed or added in app without which the existing app would be facing certain issues then use immediate update.
- When the existing app’s version in user’s device would not be supported in future then this kind of update type is really helpful to migrate those users to the current version.
Problems due to which it got into the picture:
- From the time Reliance Jio come into the picture Internet pack prices fallen too much and most of the android phone users have chosen auto update of app but still rate of updated to new version is not 100% that is why google released the auto update api to increase the app updates.
- Before releasing the google app update APIs, it was not possible to download the new version of the app in background and update it while using the app but now update experience is seamless.
Where to set flag for Flexible or Immediate Update :
To check the condition whether update is Flexible or Immediate using the code below then both functions will return true always, to identify that update is flexible or immediate we have to set a flag (in Remote Config using Firebase or by REST API) then will handle condition by joining flag coming from (api or remote config) and
appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)
OR
appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.Immediate)
Implementation Code in Kotlin:
1.Flexible Update Type:
- Check for update :
Before requesting an update firstly check if there is any update available or not and requested update type is allowed or not using this piece of code.
private fun checkForInAppUpdate() {
// First create an instance of AppUpdateManager using AppUpdateManagerFactoryClass
appUpdateManager = AppUpdateManagerFactory.create(this)
// Here appUpdateManager.appUpdateInfo return an Intent Object that will be used to start an update later
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
//Here value of updateFlag is getting from Remote config that could be 0/1 (0 for flexible update and 1 for Immediate Update)
if (updateFlag == 0 && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
requestUpdate(appUpdateInfo)
//InstallStateUpdatedListener is implemented in your activity code and handle the Install status in overridden function “onStateUpdate(install:InstallState)â€
appUpdateManager.registerListener(this@HomeActivity)
}
}
}
}
- Request For Update
If update is available and flexible update type is allowed then we will request an update and get the update status in onActivityResult().
private fun requestUpdate(appUpdateInfo: AppUpdateInfo?, updateType: Int = AppUpdateType.FLEXIBLE) {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
updateType,
this@HomeActivity,
REQUEST_CODE_APP_UPDATE
)
}
We should ideally request for the update only once unless the previously requested update fails due to some reason.
- Get Callback for Flexible Update (For Immediate update we will not get this callback)
//Here we get the callback after requesting the update that update is Success or Cancelled or Failed
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_APP_UPDATE && resultCode != RESULT_OK) {
checkForInAppUpdate() // if in any case update request is cancelled and failed
}
}
Here requestCode is an Integer value that is defined while requesting an update.
Here Result Code could have three values :
1. RESULT_OK: It means download new update is successful for Flexible Update and control is given back to app for taking necessary action to restart and update app but we will not get this callback in case of Immediate Update because till the time control will comes to the app update has been completed by google play successfully.
2.RESULT_CANCELLED: We will get the value of result as RESULT_CANCELLED When user denied to update to the new version of the app.
3.RESULT_IN_APP_UPDATE_FAILED: Due to any reason(no internet or any other issue) download of new version has been failed then will get resultCode as failed.
Handle Flexible Update
In flexible update a dialog is shown to the user in app and once the user accepted to update to new version of the app then download starts in background while user can interact with the app in the foreground, there would not be any interruption for the user until the download completes.
How would app get the callback once download is successfully done ?
When the update starts after user consent then a InstallStateUpdateListener is also registered in app that helps to get the INSTALL Status.
//Here state of update is checked and if it is downloaded then ShowSnackbar with Reload app Button
override fun onStateUpdate(state: InstallState) {
if (state.installStatus() == InstallStatus.DOWNLOADED) {
showSnackbar(getString(R.string.app_update_msg), getString(R.string.restart), onClickListener)
}
}
//Common method to show snackbar that is taking message, clickButton and clickListener
fun showSnackbar(message: String, actionText: String, clickListener: OnClickListener) {
findViewById<View>(R.id.activityView)?.let {
Snackbar.make(it, message, Snackbar.LENGTH_INDEFINITE).setAction(actionText, clickListener).show()
}
}
//Handle clickListener on click of Reload App Button on Snackbar
private val onClickListener = View.OnClickListener { view ->
//Here update will be completed in the foreground by google play by restarting app.
appUpdateManager.completeUpdate()
appUpdateManager.unregisterListener(this)
}
After calling appUpdateManager.completeUpdate() in foreground a full screen UI will show then system will update the app in background & restart the app to its main activity. In this approach Ui of app will be blocked for user until app will restart.
It is recommended to call appUpdateManager.completeUpdate() on background thread so that the UI of the app won’t be blocked at the time of update.
Handle Pending Flexible Update
After opening the app, in onResume() Callback method of Activity check is there any update exists in Downloaded State if yes then handle it gracefully using below mentioned code.
If there is any Downloaded update exists then it will unnecessarily consumes device’s storage so to install that update show a snackbar with reload button and install the update on user consent.
override fun onResume() {
super.onResume()
appUpdateManager.appUpdateInfo.addOnSuccessListener {
if (it.installStatus() == InstallStatus.DOWNLOADED) {
showSnackbar(getString(R.string.app_update_msg), getString(R.string.restart), onClickListener)
}
}
}
//Common method to show snackbar that is taking message, clickButton and clickListener
fun showSnackbar(message: String, actionText: String, clickListener: OnClickListener) {
findViewById<View>(R.id.activityView)?.let {
Snackbar.make(it, message, Snackbar.LENGTH_INDEFINITE).setAction(actionText, clickListener).show()
}
}
//Handle clickListener on click of Reload App Button on Snackbar
private val onClickListener = View.OnClickListener { view ->
//Here update will be completed in the foreground by google play by restarting app.
appUpdateManager.completeUpdate()
appUpdateManager.unregisterListener(this)
}
2.Immediate Update Type
- Check for update :
Before requesting an update firstly check if there is any update available or not and requested update type is allowed or not using this piece of code.
private fun checkForInAppUpdate() {
// First create an instance of AppUpdateManager using AppUpdateManagerFactoryClass
appUpdateManager = AppUpdateManagerFactory.create(this)
// Here appUpdateManager.appUpdateInfo return an Intent Object that will be used to start an update later
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
//Here value of updateFlag is getting from Remote config that could be 0/1 (0 for flexible update and 1 for Immediate Update)
if (updateFlag == 1 && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
requestUpdate(appUpdateInfo)
}
}
}
}
- Request Update
If update is available and Immediate update type is allowed then we will request an update using appUpdateManager.startUpdateFlowForResult and update will start in foreground . Once the update is completed google play automatically restart the app.
private fun requestUpdate(appUpdateInfo: AppUpdateInfo?, updateType: Int = AppUpdateType.IMMEDIATE) {
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
updateType,
this@HomeActivity,
REQUEST_CODE_APP_UPDATE
)
}
We should ideally request for the update only once unless the previously requested update fails due to some reason.
Handle Immediate Update If Cancelled or Fails due to any reason
In Immediate update , google play perform the whole process of updating the app in foreground
by showing a full screen UI until update installed successfully but due to any reason user left the app by pressing the home button or any other way then update will be in progress state, so next
time when app will restart we will check on main activity’s onResume() that is any update in
progress and handle the case accordingly
override fun onResume() {
super.onResume()
appUpdateManager.appUpdateInfo.addOnSuccessListener {appUpdateInfo->
//check if any update is in progress and if yes then again request for update.
if (appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
requestUpdate(appUpdateInfo, AppUpdateType.IMMEDIATE)
}
}
}
GitHub Link for code Sample: https://github.com/rahul2689/AppUpdatesSample/tree/masterlocal