Keeping your application up to date with in-app updates

Keeping your application up to date with  in-app updates

Introduction

Keeping your app up to date on your users’ devices enables them to try new features, as well as benefit from performance improvements and bug fixes. In-app updates is a Play Core library feature that introduces a new request flow to prompt active users to update their application whenever a new release is available.

In-app updates works only with devices running Android 5.0 (API level 21) or higher, and requires you to use Play Core library 1.5.0 or higher.

In this post we will learn how to keep an app updated by integrating the in-app updates feature in an android application.

This post assumes you’re already familiar with the basics of Android development with Android Studio IDE.Spring Boot.

Dependencies

To start us off, create an Empty Activity application and import the library into your application by adding the required gradle dependency to your app module as in the snippet below:.

dependencies {
  ...
  implementation 'com.google.android.play:core:1.6.3'
  ...
}

With the required dependency added, our app will support two updating user experiences

  1. Flexible: A user experience that provides background download and installation with graceful state monitoring. This experience is appropriate when it’s acceptable for the user to use the app while downloading the update. After a user accepts flexible update, Google Play handles the update download and installation but the user has to restart the app.
  2. Immediate: A full screen user experience that requires the user to update and restart the app in order to continue using the app. After a user accepts an immediate update, Google Play handles the update installation and app restart.

We will be covering both experiences in this post, so that the differences comes out clearly in code.

Common Functionalities

This section will cover functionalities shared by both experiences.

Check for update availability

Before requesting for an update, we need to first check if one is available for your app, and do to that we start by writing a method called checkAppUpdate to carry out that task. Within our method we will use getAppUpdateInfo() method of AppUpdateManager class to do the actual checking, as shown below:

protected void checkAppUpdate(){
// Creates instance of the manager.
appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          // For a flexible update, use AppUpdateType.FLEXIBLE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request the update.
    }
});
}

The result contains the update availability status. If an update is available and the update is allowed, the returned AppUpdateInfo also contains an intent to start the update. See the next section for how to start the update.

If an in-app update is already in progress, the result will also report the status of the in-progress update.

Start an update

After checking that you are able to update the app, the next step is to call AppUpdateManager.startUpdateFlowForResult()to start updating the application.

protected void checkAppUpdate(){
// Creates instance of the manager.
AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          // For a flexible update, use AppUpdateType.FLEXIBLE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              appUpdateManager.startUpdateFlowForResult(
    appUpdateInfo,
    AppUpdateType.FLEXIBLE,
    this,
    UPDATE_REQUEST_CODE);
    }
});
}

startUpdateFlowForResult() method takes four parameter:-

  • appUpdateInfo: intent that is returned by getAppUpdateInfo()  
  • appUpdateType: an integer constant determining the type of update to be performed. Pass AppUpdateType.FLEXIBLE or AppUpdateType.IMMEDIATE for flexible or immediate update respectively
  • activity: Activity requesting update. In fragment pass getActivity()
  • requestCode: unique integer code for monitoring our request

Each AppUpdateInfo instance can be used to start an update only once. To retry the update in case of failure, you need to request a new AppUpdateInfo and check again that the update is available and allowed.

Get a callback for update status

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

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == MY_REQUEST_CODE) {
    if (resultCode != RESULT_OK) {
      log("Update flow failed! Result code: " + resultCode);
      // If the update is cancelled or fails,
      // you can request to start the update again.
    }
  }
}

The following describes the different values you may receive from the onActivityResult() callback:

  • RESULT_OK: The user has accepted the update. For immediate updates, you might not receive this callback because the update should already be completed by Google Play by the time the control is given back to your app.
  • RESULT_CANCELED: The user has denied or cancelled the update.
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED: Some other error prevented either the user from providing consent or the update to proceed.

Now that we are done with the common functionalities, the next sections will deal with the specifics of the the afore mentioned experiences (Flexible and Immediate).

Flexible update

When you start a flexible update, a dialog first appears to the user to request consent. If the user consents, the download starts in the background, and the user may continue to interact with the app.

Monitoring flexible update states

After a user accepts a flexible update, Google Play begins downloading the update in the background. After the download begins, your app needs to monitor the update state to know when the update can be installed and to display the progress in your app’s UI. Monitoring is required for flexible update only. For immediate updates, Google Play takes care of downloading and installing the update for you.

You can monitor the state of an update in progress by registering a listener to install status updates.

// Create a listener to track request state updates.
 InstallStateUpdatedListener listener = state -> {
 // Show module progress };
// Before starting an update, register a listener 
updates.appUpdateManager.registerListener(listener);
//Start an update
// When status updates are no longer needed, unregister the listener.
appUpdateManager.unregisterListener(listener);

Install a flexible update

If you monitor the flexible update state and you detect the InstallStatus.DOWNLOADED state, you need to restart the app to install the update.

Unlike an immediate update, Google Play does not trigger an app restart for you. That’s because, during a flexible update, the user has an expectation to keep using the app until they decide that they want to install the update.

So, it’s recommended that you provide a notification (or some other UI indication) that informs the user that installation is ready and requests user confirmation to restart the app.

The following code sample shows a popup notification to the user after a flexible update is downloaded.

@Override
public void onStateUpdate(InstallState state) {
  if (state.installStatus() == InstallStatus.DOWNLOADED) {
    // After the update is downloaded, show a notification
    // and request user confirmation to restart the app.
    popupAlerter();
  }
  ...
}

/* Displays a notification and call to action. */
private void popupAlerter() {
        Alerter.create(this)
                .setTitle(R.string.app_update)
                .setDuration(15000) //15 secs
                .setBackgroundColorRes(R.color.colorPrimary)
                .setIcon(R.drawable.ic_stat_onesignal_default)
                .setText("An update has just been downloaded. Please restart you application to install the update")
                .addButton("Restart", R.style.AlertButton, v -> appUpdateManager.completeUpdate()).show();

    }

For the popup am using the Alerter library, so please remember to include the needed dependencies as show below:-

//alerter library
implementation 'com.tapadoo.android:alerter:4.0.2'

When you call appUpdateManager.completeUpdate() in the foreground, the platform displays a full-screen UI which restart the app in the background. After the platform installs the update, the app restarts into its main activity.

If you instead call appUpdateManager.completeUpdate() in the background, the update is installed silently without obscuring the device UI.

When the user brings your app to the foreground, it’s recommended that you check that your app doesn’t have an update waiting to be installed. If your app has an update in the DOWNLOADED state, show the notification to request that the user install the update, as shown below. Otherwise, the update data continues to occupy the user’s device storage.

// You should execute this check at all app entry points.
@Override
protected void onResume() {
  super.onResume();
  appUpdateManager.getAppUpdateInfo().addOnSuccessListener(
  appUpdateInfo -> {
   // If the update is downloaded but not installed,
   // notify the user to complete the update.
   if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED){
      popupAlerter();
   }
  });
}

Immediate update

If you are performing an immediate update, and the user consents to installing the update, Google Play displays the update progress on top of your app’s UI across the entire duration of the update. During the update, if the user closes or terminates your app, the update should continue to download and install in the background without additional user confirmation.

However, when your app returns to the foreground, you should confirm that the update is not stalled in the UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS state. If the update is stalled in this state, resume the update, as shown below:

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all entry points into the app.
@Override
protected void onResume() {
  super.onResume();

  appUpdateManager
      .getAppUpdateInfo()
      .addOnSuccessListener(
          appUpdateInfo -> {
            ...
            if (appUpdateInfo.updateAvailability()
                == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                // If an in-app update is already running, resume the update.
                manager.startUpdateFlowForResult(
                    appUpdateInfo,
                    IMMEDIATE,
                    this,
                    MY_REQUEST_CODE);
            }
          });
}

Full Code

The source code below show how to do flexible update, for immediate update please make the necessary changes as explained earlier.

import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import com.google.android.play.core.appupdate.AppUpdateInfo;
import com.google.android.play.core.appupdate.AppUpdateManager;
import com.google.android.play.core.appupdate.AppUpdateManagerFactory;
import com.google.android.play.core.install.InstallStateUpdatedListener;
import com.google.android.play.core.install.model.AppUpdateType;
import com.google.android.play.core.install.model.InstallStatus;
import com.google.android.play.core.install.model.UpdateAvailability;
import com.google.android.play.core.tasks.OnSuccessListener;
import com.google.android.play.core.tasks.Task;
import com.tapadoo.alerter.Alerter;



public class MainActivity extends AppCompatActivity {
    private static final int UPDATE_REQUEST_CODE = 100;
    private static String TAG = MainActivity.class.getSimpleName();

    @BindView(R.id.toolbar)
    Toolbar mToolbar;

    private AppUpdateManager appUpdateManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mToolbar = findViewById(R.id.toolbar);
        setSupportActionBar(mToolbar);

        checkAppUpdate();

    }

    @Override
    protected void onDestroy() {
        unregisterListener();
        super.onDestroy();
    }

    @Override
    protected void onResume() {
        super.onResume();
        checkNewAppVersionState();
    }

    private void checkAppUpdate() {
        // Creates instance of the manager.
        appUpdateManager = AppUpdateManagerFactory.create(this);

        appUpdateManager.registerListener(installStateUpdatedListener);

        // Returns an intent object that you use to check for an update.
        Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

        // Checks that the platform will allow the specified type of update.
        appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
            if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
                    // For a flexible update, use AppUpdateType.FLEXIBLE
                    && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
                // Request the update.
                try {
                    appUpdateManager.startUpdateFlowForResult(
                            // Pass the intent that is returned by 'getAppUpdateInfo()'.
                            appUpdateInfo,
                            // Or 'AppUpdateType.FLEXIBLE' for flexible updates.
                            AppUpdateType.FLEXIBLE,
                            // The current activity making the update request.
                            this,
                            // Include a request code to later monitor this update request.
                            UPDATE_REQUEST_CODE);
                } catch (IntentSender.SendIntentException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == UPDATE_REQUEST_CODE) {
            if (resultCode != RESULT_OK) {
                log("Update flow failed! Result code: " + resultCode);
                // If the update is cancelled or fails,
                // you can request to start the update again.
            }
        }
    }

    private InstallStateUpdatedListener installStateUpdatedListener = installState -> {
        // Show module progress, log state, or install the update.
        if (installState.installStatus() == InstallStatus.DOWNLOADED) {
            // After the update is downloaded, show a notification
            // and request user confirmation to restart the app.
            popupAlerter();
        }
    };

    /*
      You should execute this check at all app entry points.
     */
    private void checkNewAppVersionState() {

        appUpdateManager
                .getAppUpdateInfo()
                .addOnSuccessListener(new OnSuccessListener<AppUpdateInfo>() {
                    @Override
                    public void onSuccess(AppUpdateInfo appUpdateInfo) {

                        //FLEXIBLE:
                        // If the update is downloaded but not installed,
                        // notify the user to complete the update.
                        if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                            popupAlerter();
                            Log.d(TAG, "checkNewAppVersionState(): resuming flexible update. Code: " + appUpdateInfo.updateAvailability());
                        }

                    }
                });

    }

    private void popupAlerter() {

        Alerter.create(this)
                .setTitle(R.string.app_update)
                .setDuration(15000) //15 secs
                .setBackgroundColorRes(R.color.colorPrimary)
                .setIcon(R.drawable.ic_stat_onesignal_default)
                .setText("An update has just been downloaded. Please restart you application to install the update")
                .addButton("Restart", R.style.AlertButton, v -> appUpdateManager.completeUpdate()).show();

    }

    private void unregisterListener() {
        if (appUpdateManager != null && installStateUpdatedListener != null)
            appUpdateManager.unregisterListener(installStateUpdatedListener);
    }

}

Summary and Troubleshoot

This section describes some possible solutions to situations where in-app updates might not work as expected during testing.

  • In-app updates is not available in debug releases, the application must be signed with a release key and placed on Google Play Store
  • In-app updates are available only to user accounts that own the app. So, make sure the account you’re using has downloaded your app from Google Play at least once before using the account to test in-app updates.
  • Make sure that the app that you are testing in-app updates with has the same application ID and is signed with the same signing key as the one available from Google Play.
  • Because Google Play can only update an app to a higher version code, make sure the app you are testing as a lower version code than the update version code.
  • Make sure the account is eligible and the Google Play cache is up to date. To do so, while logged into the Google Play Store account on the test device, proceed as follows:
    1. Make sure you completely close the Google Play Store App.
    2. Open the Google Play Store app and go to the My Apps & Games tab.
    3. If the app you are testing doesn’t appear with an available update, check that you’ve properly set up your testing tracks.

Please leave your comments, suggestions and corrections in the comment section below.

Leave a Reply

Close Menu