Promoting App Updates

Joel Oliveira
Joel Oliveira
Nov 20 2020
Posted in Engineering & Technology

Letting users know when it's time to update

Promoting App Updates

An increasingly important aspect for modern mobile apps is the ability to keep an app up-to-date on users' devices. Although most users will enable background updates, some users will need to be reminded when new versions of your app are available.

Sometimes a new version implements new features or critical bug fixes that simply cannot wait until these background updates kick-in. You will want to remind those users that an update is ready to be installed.

But tackling this problem comes with some challenges. In iOS, this is not baked into any of the official frameworks while in Android you can use the Play Core library, for Google Play Services powered devices, or the HMS SDK for Huawei Mobile Services powered devices. In this post we would like to walk you through these solutions and how you would go about it.

Android (Google Play Services)

In-app updates are available for device running Android 5.0 or higher and require you to use the Play Core 1.5.0 or higher. This library will enable you to support the following strategies for in-app updates:

Flexible

Ideal for in-app updates that are not critical where you simply want to urge users to try a new feature. This option allows your app to download and install a new version while it is being used.

Immediate

This option will require the user to update and restart the app in order to continue to use your application. This user experience is ideal for updates that are critical to the core functionality of your app. After the user accepts, Google Play will handle the installation and restart your app.

Implementation

Let's dive into a quick implementation of these options. In the example below, we are requesting an Immediate update flow if one is available:

val appUpdateManager = AppUpdateManagerFactory.create(context)

val appUpdateInfoTask = appUpdateManager.appUpdateInfo

appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
        appUpdateManager.startUpdateFlowForResult(appUpdateInfo ,AppUpdateType.IMMEDIATE, this, MY_REQUEST_CODE)
    }
}

After starting an update, you can use an onActivityResult() callback to handle an update failure or cancellation, as shown below:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    if (requestCode == MY_REQUEST_CODE) {
        if (resultCode != RESULT_OK) {
            // If the update is cancelled or fails, you can request to start the update again.
        }
    }
}

For simplicity sake, we are keeping this to a bare minimum and you are encouraged to dive into all the capabilities of in-app updates by reading this guide.

Android (Huawei Mobile Services)

Devices running HMS can also take advantage of the built-in functionality to handle in-app updates. This is a more simplistic approach when compared to its Google Play Services counterpart. There is basically one single approach to in-app updates in HMS powered devices. An app will request information about an update availability and receive a callback.

Implementation

Below are the minimal steps necessary to implement in-app updates using the HMS SDK. You start by initializing the AppUpdateClient class:

AppUpdateClient client = JosApps.getAppUpdateClient(this);
client.checkAppUpdate(this, new UpdateCallBack(this));

Then you should implement the callback class that implements CheckUpdateCallBack and handles the information about a new update:

private static class UpdateCallBack implements CheckUpdateCallBack {
    private AppApiActivity apiActivity;
    private UpdateCallBack(AppApiActivity apiActivity) {
        this.apiActivity = apiActivity;
    }
    public void onUpdateInfo(Intent intent) {
        if (intent != null) {
            int status = intent.getIntExtra(UpdateKey.STATUS, DEFAULT_VALUE);
            int rtnCode = intent.getIntExtra(UpdateKey.FAIL_CODE, DEFAULT_VALUE);
            String rtnMessage = intent.getStringExtra(UpdateKey.FAIL_REASON);
            Serializable info = intent.getSerializableExtra(UpdateKey.INFO);
            if (info instanceof ApkUpgradeInfo) {
                ApkUpgradeInfo upgradeInfo = (ApkUpgradeInfo) info;
                client.showUpdateDialog(this.apiActivity, upgradeInfo, false);
            }
        }
    }
}

For more information about this API, we strongly urge you to read this guide.

iOS

Unfortunately Apple does not provide any built-in functionality to handle in-app updates. There are some very good 3rd party projects like this one that help you remind users of a new version. However, in this post, we will explore a solution that you can build yourself.

Implementation

The most logical approach in iOS would be to look up the last version of your app released in the App Store. Luckily, Apple still provides an endpoint where we can look up an app's last release information. From there, you can then compare with your app's current version and figure out if an update is needed. This would need the following helper method:

-(BOOL)needsUpdate:(NSString*)appID forVersion:(NSString*)currentVersion{
    NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"http://itunes.apple.com/lookup?id=%@", appID]];
    NSData* data = [NSData dataWithContentsOfURL:url];
    NSDictionary* lookup = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

    if ([lookup[@"resultCount"] integerValue] > 0){
        NSString* appStoreVersion = lookup[@"results"][0][@"version"];
        if ([appStoreVersion compare:currentVersion options:NSNumericSearch] == NSOrderedDescending) {
            return YES;
        }
    }
    return NO;
}

Then we could use this method to trigger a dialogue that warns the user of an update. Users can then be redirected to the App Store if they decide to update:

NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
NSString* appID = infoDictionary[@"CFBundleIdentifier"];
NSString* currentVersion = infoDictionary[@"CFBundleShortVersionString"];

if ([self needsUpdate:appID forVersion:currentVersion]) {

    UIAlertController* alert = [UIAlertController
                             alertControllerWithTitle:@"Warning"
                             message:@"New version available, do you want to update?"
                             preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction* update = [UIAlertAction
                             actionWithTitle:LS(@"Update")
                             style:UIAlertActionStyleDefault
                             handler:^(UIAlertAction * action){

                                NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:@"itms://itunes.apple.com/app/apple-store/id%@?mt=8", appID]];
                                [[UIApplication sharedApplication] openURL:url];

                             }];
    [alert addAction:update];

    UIAlertAction* cancel = [UIAlertAction
                             actionWithTitle:LS(@"Cancel")
                             style:UIAlertActionStyleCancel
                             handler:nil];
    [alert addAction:cancel];

    [self presentViewController:alert animated:YES completion:nil];

}

Conclusion

Although some work is involved to provide such a feature in your app, it becomes more and more important to consider these mechanisms, specially for apps with a fast pace development strategy, where new features are always around the corner and bug fixes happen at the speed of light.

As always, we remain available for any suggestions, corrections or questions you might have, via our Support Channel.

Keep up-to-date with the latest news