Do you know what’s one of the things that makes me envious of web developers? It is the fact that without relying on in-app updates they can release anything anytime and can be sure that all the users are going to see their new code almost instantly.
For software developers writing code for mobile apps the story is a bit different. The code we write for the mobile apps after every release has to go through the stores’ review process and then slowly gets on to the devices of our wonderful users.
In-App updates by Google Play Core library are here to change the game.
Default Update Behaviour for Android Apps
In theory, updates on Android apps occur automatically, once the following constraints are met:
- The device is connected to the Internet over Wi-Fi.
- It is plugged in and charging.
- The device is idle and not actively used.
- The app that has to be updated is not running in the foreground.
As per Google Play’s help page, we know that devices check for updates once a day. This means that it will be at most 24 hours before an app update is added to the update queue.
Default Updates: The Reality
While we expect the users to be on the latest versions of our apps, the reality is a tad different. There may be too many apps in the update queue, automatic updates turned off, or the app may have been in the foreground when it was about to get the update.
Analysis of the app version adoption graphs of your releases will teach you you a number of interesting things:
- Majority of your app versions are never adopted by 100% of your users. (Newly launched apps will be an anomaly in this case.)
- Adoption acceleration tends to zero when you hit 75%-80% adoption rate.
- You have to wait for several months if you want, say, 95% of your users to be off of legacy versions of your app.
For minor changes, and regular updates it doesn’t really matter a lot and it is completely ok to have a slow and gradual adoption. The real need to get majority users on the latest version arises in case you have a critical fix, or if you want to deprecate a particular version due to any reason. That’s when you want to nudge your users to update the app if they haven’t.
How did we Force In-App Updates till now?
If you have been writing apps for at least a year, you might have found various creative solutions to ask your users to get the latest version of the app. Don’t judge me when I mention some that I have personally used:
- Sending silent PUSH notifications to tell the clients that an update is available.
- Using clients to POLL the backend to check if there is an update available?
- Scraping Google Play web page in the app to see if the update is available.
If you or your company use another approach to solve this problem. I’d love to learn more about it. Hit me up on Twitter 👉 @droidchef
What’s new: The In-App Updates API?
In-App Updates is a feature of the Google Play Core Library which allows you to prompt your users to update the app when a new version is available. Minimum requirements for it to work, include:
- Devices running Android 5.0 (API level 21) or higher.
- Google Play Core library version 1.5.0 or higher.
Types of In-App Updates User Experience
Flexible Updates
This UX leads the user to a small dialog offering an update which can happen in the background and the user can continue using the app. Once the update has been downloaded and installed, user will be prompted to restart the app so that the changes can come into effect.
It is useful when you have a new feature that you’d want your users to try.
Immediate Updates
This UX leads the user into a full screen dialog, prompting the user to update the app in the foreground and prevent the user from using the app during this operation. In this flow, Google Play will handle the restart operation of the app as well.
They are useful when you have a critical update that you want your users to install.
Next we will learn how to implement Immediate Updates flow into your android apps.
Getting the dependency
If you don’t already use the Google Play Core library, you can add it to your app’s build.gradle file like this. As of writing this article, the latest version of the library is 1.7.1
implementation "com.google.android.play:core:1.7.1"
This dependency is downloaded from Google’s Maven Repository. So make sure you also include that repository in your project’s
build.gradle
file.
Checking for Update Availability
Before you can actually launch the update flow, you must check if there is an update available for the user. To do that, we will ask the AppUpdateManager
about it.
Let us say we want to trigger the prompt from our MainActivity
.
This could be a different entry point for your app so choose it as per your need. I will just refer to the MainActivity as our entry point for the purpose of this article.
Now, open your activity file and create an empty method called checkForAppUpdates()
private fun checkForAppUpdates() { // Logic for checking and triggering updates goes here }
Logic for checking and triggering updates will go here, so let’s get started.
First, we need to get hold of the AppUpdateManager
private fun checkForAppUpdates() { val appUpdateManager = AppUpdateManagerFactory.create(context) }
Next, we ask the manager about the update information.
private fun checkForAppUpdates() { val appUpdateManager = AppUpdateManagerFactory.create(context) val appUpdateInfoTask = appUpdateManager.appUpdateInfo }
Now the appUpdateInfoTask
that we got is an Intent
that you will use to check for the update
private fun checkForAppUpdates() { val appUpdateManager = AppUpdateManagerFactory.create(context) val appUpdateInfoTask = appUpdateManager.appUpdateInfo appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) { // Request the update. } }
Starting an Update
In case there is an update available, we we will request android to start the activity for the update
private fun checkForAppUpdates() { val appUpdateManager = AppUpdateManagerFactory.create(context) val appUpdateInfoTask = appUpdateManager.appUpdateInfo appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) { appUpdateManager.startUpdateFlowForResult( appUpdateInfo, AppUpdateType.IMMEDIATE, this, 7500) // REQUEST_CODE_IN_APP_UPDATE = 7500 } }
7500 is a request code we are using for the activity we are starting for result. You can choose any number that is unique for your app and can store it in the place you aggregate your
Constant
s.
Getting Ready for the Action
Next, call this method from the onCreate()
of your activity.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... existing logic checkForAppUpdates() }
With all this code so far, you should be able to get the ball rolling.
Building the APKs or app Bundles
Since we need to emulate a real release process, we need to build an APK or app bundles that we are going to use for testing. Installing the app straight away from Android Studio or command line will not work.
To assemble the release we can simply ask Gradle to do it. Make sure you are in the project directory and then run this command.
First build one version of the app.
Let’s call it 1.0.0
and version code 1
.
Then build the second version of the app, which will act as an update for the previous one.
Let’s call it 1.0.1
and version code 2
.
If you have a lot of flavours in your project you can also build a specific flavour in case that makes things faster for you to test.
For simplicity, I’ll just run this command.
./gradlew assembleRelease
Finally when this task finishes successfully. You should have your APKs or app bundles ready. I assume you know where to find your release APK or app bundles.
Testing In-App Updates
You need a Google Play Developer account for this step. We are going to leverage internal app sharing feature of the Google Play Console to test our in-app updates. If you’re not familiar with it, I’d highly recommend you to first look at it and then come back to the article. As that link contains some crucial steps related to adding accounts that have access to your test apps, and also how to enable internal app sharing in your Google Play app on your device.
Head over to the internal app sharing upload page. On this page you’ll see an option to upload the release assets.
Uploading APKs and app Bundles
Drag and drop the first version here and once the upload finishes you will have a download link below.
Now do the same thing with your second version and have a link ready before we move on to the next step. The download links on that page should look like this.
Finally make sure you don’t have any other versions of this app on your device.
Installing the First Version
Now carefully copy the link of version 1.0.0
and use it to install this app on your device.
Using this link on the device, will take you to a screen that looks something like this
Click on install, to start the installation. Once the installation is finished. Open the app and head over to the activity where you put your in-app update logic.
Nothing should happen at this point. You might be wondering why am I not getting the update? 🤔
This is because Google Play still doesn’t know if an update is available or not.
Notifying Google Play
Next, copy the second version’s link and open that on your device. It should open a screen that looks like this. Please DO NOT click the update button here.
This step ensures, Google Play knows that there is an update available.
Now make sure you kill your app from the background.
Updating the app
Start your app and head over to the activity that has your in-app update logic. You will see the in-app update screen soon after the activity appears.
Finally you can click on the update button and let Google Play do its magic.
Once the update finishes, your app will restart automatically. You can verify the version name by looking into the app settings as well.
Handling a Stalled Update
As all users are not patient or there might be a scenario where the user closes or terminates your app during the update. Google Play is smart enough to make sure that the update will continue to download and install in the background, however we must ensure that it is applied correctly when the user returns to the foreground.
Fortunately, the library has us covered for that scenario too. Remember how we were able to check if there is an update available using UpdateAvailability.UPDATE_AVAILABLE
state.
There is another state that you can compare the availability against which is called UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
.
You can add an OR condition for checking this state too and you should be covered.
private fun checkForAppUpdates() { val appUpdateManager = AppUpdateManagerFactory.create(context) val appUpdateInfoTask = appUpdateManager.appUpdateInfo appUpdateInfoTask.addOnSuccessListener { appUpdateInfo -> if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE || appUpdateInfo.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) { appUpdateManager.startUpdateFlowForResult( appUpdateInfo, AppUpdateType.IMMEDIATE, this, REQUEST_CODE_IN_APP_UPDATE) } }
Since the user can just come back from the background without killing the app during the update, you might also want to call the update check from onResume()
like this.
override fun onResume() { super.onResume() checkForAppUpdates() }
Reacting to Update Status Callbacks
If you have a keen eye for the code, 🧐 you might have figured out that we are actually starting an activity for result. This leads us to our final piece of handling failed updates. For simplicity, we just check if the result was not ok, we try to prompt the user for the update again as we are dealing with immediate in-app updates right now.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { if (requestCode == REQUEST_CODE_IN_APP_UPDATE) { if (resultCode != RESULT_OK) { checkForAppUpdates() } } }
Preventing Excessive Update Checks
As a smart developer that you are 😎, I am sure you have a question in your mind. Aren’t we going to check for updates too frequently? The answer is yes, with this approach you will end up checking for updates every time your activity’s onCreate()
or onResume()
is called.
As we are currently only dealing with Immediate updates, they should only be intended for critical updates. Ideally you should guard the check with some short-circuiting logic.
The way I work around this problem is by using a Feature Flag surrounding the checkForAppUpdates()
. The way we intend to use it, is by targeting specific versions of the app. So the flag only returns true for specific version and otherwise false. Using this we can target our users on a specific version to perform an immediate in-app whenever there is a need for one.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (myFeatureFlagManager.isFlagEnabled(FeatureFlag.IN_APP_UPDATE) ) { checkForAppUpdates() } }
What did you learn today?
If you’ve followed the post till here, 👏 Congratulations, you’ve equipped yourself with some knowledge about how to use in-app updates from Google Play Core library in Android. If you manage to get this working in your app, I’d love to try it out as a user.
Tell me about your app here 👉 @droidchef
What is Coming Next?
In the next blog, we will learn how to implement and handle flexible in-app updates.
Where to Go From Here?
Want to learn more about the In-App Updates. You can head over to Google’s official documentation here.
If you are facing any problems with testing in-app updates, there is a specific troubleshooting section on the same page.
If you like this post, please show your support by sharing it and if you spot any issues here, don’t hesitate to shoot them my way. Let’s connect on LinkedIn and Twitter.