Handling Android in-app updates at Halodoc

App-update Dec 18, 2019

Halodoc embraces being agile. With a release cadence of 2 weeks, we are constantly shipping new features that take us closer to our vision of Simplifying Healthcare. And while we would love to see our users on the latest and greatest version of our APP, there are some constraints that come into play. For instance, some users enable background updates when their device is connected to an unmetered connection while others users may need a reminder. This is where Android in-app updates comes to our rescue.

Google recently released In-app updates for devices running Android 5.0 (API level 21) or higher. This feature lets creators nudge their users to update their apps without taking them to the play store.

Starting off, the Play Core library offers us two distinct ways to prompt our users when an update is available — the Flexible or the Immediate approach.

Image Description
Image Description
Flexible update in halodoc
Image Description
Immediate update in halodoc

Prerequisites

To be able to use this feature, we should have:

  1. Play core library
  2. API should be 21 and above
dependencies {
    implementation 'com.google.android.play:core:1.6.4'
}

Strategy to prompt app update

We configured the update to be flexible or immediate via our config (an API call that is made every time the user launches our app). Along with this, we also configured the frequency of this update nudge in the shared preference. We are updating these values on app update and when user logs out from app.

Image Description

Check for update availability

Before requesting an update, a check should be made to confirm if there is one available. To check for an update, we use AppUpdateManager. This class of the PlayCore library is used to get the AppUpdateInfo object. That object has information on update availability, available version, and some additional statuses.

val updateManager = AppUpdateManagerFactory.create(this)

updateManager.appUpdateInfo.addOnSuccessListener {
    if (it.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE &&
        it.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
        requestAppUpdate()
    }
}

Request an update

We are also careful on how often we prompt the users to update in order to avoid tiring our users.
Additionally, each AppUpdateInfo instance can be used to start an update only once. To retry the update in the case of a failure, a new AppUpdateInfo must be requested.

private fun requestAppUpdate(appUpdateInfo: AppUpdateInfo?) {
    appUpdateManager.startUpdateFlowForResult(
        appUpdateInfo,
        AppUpdateType.FLEXIBLE,
        this,
        REQUEST_CODE_UPDATE
    )
}

After starting an update, we used the onActivityResult() callback to handle an update failure or cancellation.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    if (requestCode == REQUEST_CODE_UPDATE) {
        if (resultCode != RESULT_OK) {
           when (resultCode) {
            Activity.RESULT_OK -> { //user's approval }
            Activity.RESULT_CANCELED -> { //user's rejection }
            ActivityResult.RESULT_IN_APP_UPDATE_FAILED -> { //update failure }
        }
    }
}

Handle a flexible update

Image Description

During the flexible update, the content will be downloaded in the background leaving the user free to browse the app. After the user accepts the flexible update, Google Play begins downloading it in the background.
Once the download begins, the app needs to monitor it to convey relevant information to the user such as the progress and when the update is ready to be installed.

val listener = { state ->
    // Show module progress, log state, or install the update.
}
appUpdateManager.registerListener(this)

To install the flexible update the app needs to listen to the status Status.DOWNLOADED

override fun onStateUpdate(state: InstallState) {

    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        // and request user confirmation to restart the app.
        notifyUpdate()
    }
    
}

Once done, the user can be notified to restart the app and the listener can be unregistered.

private fun notifyUpdate() {
        GenericBottomSheetDialogFragment.Builder()
            .setMessage(activity.getString(R.string.app_restart))
            .setPositiveBtnText(activity.getString(R.string.btn_restart))
            .setCancelable(true)
            .setClickListenerCallbacks(object: ICallback {
                    override fun onPositiveButtonClick(code: Int, b: Bundle?) {
                    appUpdateManager.completeUpdate()
                }
                    override fun onNegativeButtonClick(requestCode: Int) {
                }
            })
            .build()
            .show(this, "")
    }

Handle a immediate update

Image Description

Immediate in-app updates can be triggered by using the AppUpdateType.IMMEDIATE value. This trigger surfaces a blocking UI. During the update, if the app is closed or terminated, the update will continue to download and install in the background without additional user confirmation.

override fun onResume() {
    super.onResume()
    appUpdateManager.appUpdateInfo.addOnSuccessListener {
        if (it.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
            appUpdateManager.startUpdateFlowForResult(
                it,
                AppUpdateType.IMMEDIATE,
                this,
                REQUEST_CODE_UPDATE
            )
        }
    }
}

UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS means that an immediate update has been started, and update is still in progress. Triggering the request flow using update info’s intent will ask Google Play to show that blocking, immediate app update screen. Post the update, Google Play will automatically restart the app.

Backward compatibility

For devices below API level 21(Lollipop), we navigate the users directly to the play store.

Testing

  • In-app updates are available only to user accounts that own the app. So, make sure the account being used has downloaded the app from Google Play at least once before using the account to test in-app updates.
  • Because Google Play can only update an app to a higher version code, make sure the app you are testing is on a lower version code than the update version code.

Conclusion

In this article we have learnt 2 types of update: Flexible and Immediate. The flexible update is a great fit when the app has very few updates and they do not affect the core functionality. Immediate update on the other hand works very well for critical security flaws and high priority bugs.

We are always looking out to hire for all roles in our tech team. If challenging problems that drive big impact enthral you, do reach out to us at careers.india@halodoc.com


About Halodoc

Halodoc is the number 1 all around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our teleconsultation service, we partner with 1500+ pharmacies in 50 cities to bring medicine to your doorstep, we partner with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allows patients to book a doctor appointment inside our application.We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates foundation, Singtel, UOB Ventures, Allianz, Gojek and many more. We recently closed our Series B round and In total have raised USD$100million for our mission.Our team work tirelessly to make sure that we create the best healthcare solution personalized for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.