OkCupid's Android team releases new features and bug fixes every week. In a short amount of time the app can see an improvement in performance, user experience, and even a revenue increase. Naturally we want as many users as possible to receive these improvements, but weekly app updates might seem excessive and not all users have automatic updates enabled. We have implemented solutions for this problem before and after In App Updates were supported by Google's Play Core library.

Pre - In App Updates Solution

forceupdate-1

At the start of the application, we make a network request to initialize the experience. The current version that the user is on is passed as a parameter to this request and the response includes a boolean flag for whether we should force updates. Relying on this field requires a web developer to manually update the conditional of that boolean whenever we decide to amp up the mandatory version. It also depends on us to assure the value for the current version parameter is up to date.
If forceUpdate == true, the user would be presented with a modal that would redirect them to our app's play store listing.

fun openPlayStore() {
        val intent = Intent(Intent.ACTION_VIEW)
        intent.data = Uri.parse(Constants.PLAY_STORE_LINK)
        startActivity(intent)
    }

Post - In App Updates solution

Google's Play Core library now supports in app updates. Check out the docs here to see what is required for set up.

In order to implement an update flow we had to do the following;

1. Determine if we want to

  • Force user to update
  • Suggest to update
  • Do nothing

In order to make this determination we use Firebase Remote Config. Firebase grants the Android team the ability to change a property for updates without relying on a change to be made to the API by another team. The value for our designated in app update property is either NO_UPDATE, IMMEDIATE_UPDATE or FLEXIBLE_UPDATE. After a user is logged in, we check Firebase to see if we should prompt an update.

fun promptUpdate(none: Optional.None) {
            val versionFromFirebase =
                FirebaseRemoteConfig
                .getInstance()
                .getString(FirebaseConstants.IN_APP_UPDATE_PROPERTY)
            if (versionFromFirebase.isNotEmpty()) {
                val updateType = Integer.valueOf(versionFromFirebase)
                if (updateType != FirebaseConstants.NO_UPDATE) {
                    updateManager.checkIfUpdateIsAvailable(updateType, this)
                }
            }
    }

2. Check the play store for a more current version

The library determines if there is a more current app version on the play store via what seems like magic. When testing this, use a release build with the same signature as your app on the play store. Also, hard code a previous version for the APK that you are using to test.

fun checkIfUpdateIsAvailable(updateType: Int, listener: UpdateCheckListener){
       appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
            if (appUpdateInfo.updateAvailability() ==                                         UpdateAvailability.UPDATE_AVAILABLE &&
                appUpdateInfo.isUpdateTypeAllowed(updateType)
            ) {
                updateAvailable = appUpdateInfo.availableVersionCode()
                listener.onUpdateAvailable(appUpdateInfo)
            }
        }
    }

If there is a newer version available for download and we only want to suggest an update, we want to make sure that we have not pestered the user to update to the same version previously. If we make the update mandatory, the user will not have the option to opt out. Every time we prompt a user to update, we store that version number in shared preferences and we check that value before we prompt the user again. If they did not previously deny the update then we show the prompt to download the update. You can listen for the result of the prompt in your activity's onActivityResult.

fun onUpdateAvailable(appUpdateInfo: AppUpdateInfo) {
        val lastUpdateVersionDenied = //value stored in sharedprefs
        val userDeniedCurrentUpdate = (lastUpdateVersionDenied ==                                                    updateAvailable)
        if (!userDeniedCurrentUpdate) {
        //this method will give users option to download if updateType is      FLEXIBLE and require the update if it is IMMEDIATE
            appUpdateManager.startUpdateFlowForResult(
                    appUpdateInfo,
                    updateType,
                    activity,
                    Constants.IN_APP_UPDATE_REQUEST_CODE
                )
        }
    }

in 'onActivityResult'

when(result){
   Constants.IN_APP_UPDATE_REQUEST_CODE -> {
                when (requestCode) {
                    Activity.RESULT_CANCELED -> {
                        updateManager.unregisterListener()
                        //store update version that was denied in sharedprefs
                    }
                    ActivityResult.RESULT_IN_APP_UPDATE_FAILED -> {
                        //notify user that download failed via snackbar
                    }
                } 
   }

3. Start the update

Once we've confirmed that an update is available and that this is the first time the user is being prompted to update to this version we can continue with the actual download. Calling startUpdateFlowForResult will start the download and the InstallStateUpdatedListener will listen for the result. We implemented custom a snackbar to inform the user about the status of the download and to allow the user to install the update.

val flexibleUpdateListener: InstallStateUpdatedListener = object : InstallStateUpdatedListener {
        override fun onStateUpdate(installState: InstallState?) {
            val installStatus = installState?.installStatus()
            val currentUpdateType = updateType
            if (installStatus != null && currentUpdateType != null) {
                activity.showInAppInstallUpdateSnackbar(installStatus,                       currentUpdateType)
            }   
    }

Conclusion

Implementing In App Updates makes the update process seamless for developers and users alike. As developers, we now have a more configurable way to suggest or require that users update their app. For the user, if they now have the freedom to choose between updating instantly or in the background - allowing them to continue using the product without missing out on our great new features and most importantly finding their perfect match.
happy spongebob