Support in-app updates

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. Although some users enable background updates when their device is connected to an unmetered connection, other users may need to be reminded to update. In-app updates is a Play Core library feature that introduces a new request flow to prompt active users to update your app.

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. After meeting these requirements, your app can support the following UX for in-app updates:

  • Flexible: A user experience that provides background download and installation with graceful state monitoring. This UX is appropriate when it’s acceptable for the user to use the app while downloading the update. For example, you want to urge users to try a new feature that’s not critical to the core functionality of your app.

    Figure 1. An example of a flexible update flow

  • Immediate: A full screen user experience that requires the user to update and restart the app in order to continue using the app. This UX is best for cases where an update is critical for continued use of the app. After a user accepts an immediate update, Google Play handles the update installation and app restart.

    Figure 2. An example of an immediate update flow

This page shows you how to use the Play Core library to request and perform a flexible or immediate in-app update.

Check for update availability

Before requesting an update, you need to first check if one is available for your app. To check for an update, use AppUpdateManager, as shown below:

Kotlin

// Creates instance of the manager.
val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// 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.
    }
}

Java

// 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)) {
              // 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, you can request an update using AppUpdateManager.startUpdateFlowForResult(), as shown below. However, you should be mindful how often you request updates to avoid annoying or tiring your users. That is, you should limit requesting in-app updates to only the changes that are important to the functionality of your app.

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // Or 'AppUpdateType.FLEXIBLE' for flexible updates.
    AppUpdateType.IMMEDIATE,
    // The current activity making the update request.
    this,
    // Include a request code to later monitor this update request.
    MY_REQUEST_CODE)

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // Or 'AppUpdateType.FLEXIBLE' for flexible updates.
    AppUpdateType.IMMEDIATE,
    // The current activity making the update request.
    this,
    // Include a request code to later monitor this update request.
    MY_REQUEST_CODE);

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.

The type of update you request determines the next steps you need to take. To learn more, read the section about how to either Handle an immediate update or Handle a flexible update.

Get a callback for update status

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

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    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.
        }
    }
}

Java

@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.

Handle a 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. This section describes how to monitor and complete a flexible in-app update.

Monitor the flexible update state

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.

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

Kotlin

// Create a listener to track request state updates.
val listener = { state ->
    // Show module progress, log state, or install the update.
}

// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(listener)

// Start an update.

// When status updates are no longer needed, unregister the listener.
appUpdateManager.unregisterListener(listener)

Java

// Create a listener to track request state updates.
InstallStateUpdatedListener listener = state -> {
    // Show module progress, log state, or install the update.
  };

// Before starting an update, register a listener for 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.

For example, you can implement a snackbar with Material Design requests confirmation from the user to restart the app, as shown in figure 1.

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

Kotlin

override fun onStateUpdate(state: InstallState) {
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        // After the update is downloaded, show a notification
        // and request user confirmation to restart the app.
        popupSnackbarForCompleteUpdate()
    }
    ...
}

/* Displays the snackbar notification and call to action. */
fun popupSnackbarForCompleteUpdate() {
    Snackbar.make(
        findViewById(R.id.activity_main_layout),
        "An update has just been downloaded.",
        Snackbar.LENGTH_INDEFINITE
    ).apply {
        setAction("RESTART") { appUpdateManager.completeUpdate() }
        setActionTextColor(resources.getColor(R.color.snackbar_action_text_color))
        show()
    }
}

Java

@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.
    popupSnackbarForCompleteUpdate();
  }
  ...
}

/* Displays the snackbar notification and call to action. */
private void popupSnackbarForCompleteUpdate() {
  Snackbar snackbar =
      Snackbar.make(
          findViewById(R.id.activity_main_layout),
          "An update has just been downloaded.",
          Snackbar.LENGTH_INDEFINITE);
  snackbar.setAction("RESTART", view -> appUpdateManager.completeUpdate());
  snackbar.setActionTextColor(
      getResources().getColor(R.color.snackbar_action_text_color));
  snackbar.show();
}

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.

Kotlin

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all app entry points.
override fun onResume() {
    super.onResume()

    appUpdateManager
        .appUpdateInfo
        .addOnSuccessListener { appUpdateInfo ->
            ...
            // If the update is downloaded but not installed,
            // notify the user to complete the update.
            if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                popupSnackbarForCompleteUpdate()
            }
        }
}

Java

// Checks that the update is not stalled during 'onResume()'.
// However, 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) {
                  popupSnackbarForCompleteUpdate();
              }
          });
}

Handle an 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:

Kotlin

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

    appUpdateManager
        .appUpdateInfo
        .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
                );
            }
        }
}

Java

// 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);
            }
          });
}

Troubleshoot

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

  • 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.