How to create (and test) an app update listener
Running code when your app updates can be a useful marketing tool, and a reliable way of enabling new functionality only when the user updates.
For example, your app may send a message to your server when it updates, so your server knows it can now return more types of content. Similarly, you may want to send a notification to the user (from the app) when it updates, informing them of new content.
Whilst there’s plenty of existing documentation on registering an update listener, much of it is outdated, unsafe, or doesn’t include any information on how to test your implementation. This article will hopefully address these shortcomings, and is also available as a repository and a Gist.
The approach is very simple; we’re just registering an app update listener in the manifest, and then displaying a toast message inside this listener.
Registering receiver
The MY_PACKAGE_REPLACED
intent action is only fired when your app is updated. It is only available on APIs 12+ (which should be fine for almost all apps), and replaces the existing PACKAGE_REPLACED
. The drawback of the previous technique is it would often be called when any app updates, causing your app to be woken up extremely frequently.
This receiver (.AppUpgradeReceiver
in this case) is registered in your AndroidManifest.xml
, within the application
tag:
<receiver android:name=".AppUpgradeReceiver">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
Handling update intent
Now we need to create the AppUpgradeReceiver
receiver we just defined. This class extends BroadcastReceiver
, which has an onReceive
that needs overriding:
class AppUpgradeReceiver : BroadcastReceiver() {
@SuppressLint("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null) {
return
}
Toast.makeText(context, "Updated to version #${BuildConfig.VERSION_CODE}!", Toast.LENGTH_LONG).show()
}
}
Note that lint complains that we’re not filtering the incoming intent (to make sure it’s for our package), but this is not needed as we are using the MY_PACKAGE_REPLACED
intent. The SuppressLint
annotation should only be used when you are intentionally disagreeing with lint’s analysis, as in this situation!
And that’s it! Your listener is registered and will show a Toast message of the new version code whenever your app updates.
Update (2022-10-06): A helpful commenter (see bottom of post) mentioned that instead of suppressing the lint warning, you can use replace the check with if (context == null || intent?.action != "android.intent.action.MY_PACKAGE_REPLACED") {
. This is likely a better solution!
Testing
Testing this functionality initially seems intimidating, as uploading a new version to the Play Store every time would be very time consuming; luckily, we can update apps yourselves!
Previously, testing just required running the new version of your app from Android Studio. Now however, the MY_PACKAGE_REPLACED
intent can only be sent by the OS, so is not triggered during normal development.
The solution is to actually install the app manually:
- Increasing the
versionCode
in your app-levelbuild.gradle
(so it counts as an update). - Clicking
Build
->Build Bundle(s) / APK(s)
->Build APK(s)
, and selecting a debug APK. - Entering the following command into the
Terminal
tab at the bottom of Android Studio:
adb install -r <path to your apk>
For example, the command on a Windows machine to update this tutorial’s app may look like:
adb install -r C:\\Repositories\\updatelistener\\app\\build\\outputs\\apk\\debug\\app-debug.apk
You should see “Success” underneath your command in the terminal.
The app is then updated like normal, and your listener called. In the sample repo, an update will cause the new version code to be displayed in a Toast.