Compatibilidad con las actualizaciones integradas en la app (Kotlin o Java)

En esta guía, se explica cómo admitir las actualizaciones integradas en tu app usando Kotlin o Java. Hay guías separadas para casos en los que tu implementación usa código nativo (C/C++) y otros en los que ella usa Unity.

Cómo configurar tu entorno de desarrollo

La Biblioteca de actualizaciones integradas en la app de Play forma parte de las bibliotecas de Google Play Core. Incluye la siguiente dependencia de Gradle a fin de integrar la biblioteca de actualizaciones integradas en la app de Play.

Groovy

// In your app’s build.gradle file:
...
dependencies {
    // This dependency is downloaded from the Google’s Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation 'com.google.android.play:app-update:2.1.0'

    // For Kotlin users also add the Kotlin extensions library for Play In-App Update:
    implementation 'com.google.android.play:app-update-ktx:2.1.0'
    ...
}

Kotlin

// In your app’s build.gradle.kts file:
...
dependencies {
    // This dependency is downloaded from the Google’s Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation("com.google.android.play:app-update:2.1.0")

    // For Kotlin users also import the Kotlin extensions library for Play In-App Update:
    implementation("com.google.android.play:app-update-ktx:2.1.0")
    ...
}

Cómo comprobar la disponibilidad de actualizaciones

Antes de solicitar una actualización, verifica si hay alguna disponible para tu app. Para ello, utiliza AppUpdateManager:

Kotlin

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
        // This example applies an immediate update. To apply a flexible update
        // instead, pass in AppUpdateType.FLEXIBLE
        && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
    ) {
        // Request the update.
    }
}

Java

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
          // This example applies an immediate update. To apply a flexible update
          // instead, pass in AppUpdateType.FLEXIBLE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request the update.
    }
});

La instancia AppUpdateInfo que se muestra contiene el estado de disponibilidad de la actualización. Según el estado de la actualización, la instancia también contiene lo siguiente:

  • Si hay una actualización disponible y está permitida, la instancia que se muestre también contendrá un intent para iniciar la actualización.
  • Si una actualización integrada en la app ya está en curso, la instancia también informa el estado de esa actualización.

Cómo verificar la obsolescencia de las actualizaciones

Además de comprobar si hay actualizaciones disponibles, te recomendamos que verifiques cuánto tiempo pasó desde que el usuario recibió una notificación sobre una actualización a través de Google Play Store. Esto puede ayudarte a decidir si debes iniciar una actualización flexible o una actualización inmediata. Por ejemplo, podrías esperar unos días antes de notificar al usuario con una actualización flexible y, luego, solicitar una actualización inmediata.

Usa clientVersionStalenessDays() para verificar cuántos días pasaron desde que apareció la actualización en Play Store:

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

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

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && (appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
    }
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

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

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.clientVersionStalenessDays() != null
          && appUpdateInfo.clientVersionStalenessDays() >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
    }
});

Cómo verificar la prioridad de las actualizaciones

La API de Google Play Developer te permite establecer la prioridad de cada actualización. De esta manera, tu app puede determinar la insistencia con la que recomendará una actualización al usuario. Por ejemplo, considera la siguiente estrategia para establecer la prioridad de las actualizaciones:

  • Mejoras menores en la IU: Actualización de baja prioridad. No solicites una actualización flexible ni una actualización inmediata. Actualiza la app solo cuando el usuario no esté interactuando con ella.
  • Mejoras en el rendimiento: Actualización de prioridad media. Solicita una actualización flexible.
  • Actualización crítica de seguridad: actualización de prioridad alta. Requiere una actualización inmediata.

Para determinar la prioridad, Google Play usa un valor entero entre 0 y 5, en el que 0 es la prioridad predeterminada y 5 es la más alta. A fin de establecer la prioridad para una actualización, usa el campo inAppUpdatePriority en Edits.tracks.releases en la API de Google Play Developer. Se considerará que todas las versiones agregadas en la original tendrán establecida la misma prioridad que esta última. Solo se puede establecer la prioridad al momento de lanzar una versión nueva; no se puede cambiar más tarde.

Establece la prioridad mediante la API de Google Play Developer como se describe en la documentación de la API de Play Developer. La prioridad de la actualización integrada en la app debe especificarse en el recurso Edit.tracks que se pasa en el método Edit.tracks: update. En el siguiente ejemplo, se muestra cómo lanzar una app con código de versión 88 y inAppUpdatePriority 5:

{
  "releases": [{
      "versionCodes": ["88"],
      "inAppUpdatePriority": 5,
      "status": "completed"
  }]
}

En el código de tu app, puedes verificar el nivel de prioridad de una actualización determinada con updatePriority(). La prioridad que se muestra tiene en cuenta inAppUpdatePriority para todos los códigos de versión de la app entre la versión instalada y la última disponible independientemente del segmento. Por ejemplo, considera la siguiente situación:

  • Liberas la versión 1 en un segmento de producción sin prioridad.
  • Lanzas la versión 2 en un segmento de pruebas internas con prioridad 5.
  • Publicas la versión 3 en un segmento de producción sin prioridad.

Cuando los usuarios de producción actualicen de la versión 1 a la 3, tendrán prioridad 5, a pesar de que la versión 2 se publicó en un segmento diferente.

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

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

// Checks whether the platform allows the specified type of update,
// and checks the update priority.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.updatePriority() >= 4 /* high priority */
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
    }
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

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

// Checks whether the platform allows the specified type of update,
// and checks the update priority.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.updatePriority() >= 4 /* high priority */
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
    }
});

Cómo iniciar una actualización

Después de confirmar que hay una actualización disponible, puedes solicitar una actualización con AppUpdateManager.startUpdateFlowForResult():

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build())

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());

Cada instancia de AppUpdateInfo se puede usar para iniciar una actualización solo una vez. Para reintentar la actualización en caso de error, solicita una AppUpdateInfo nueva y vuelve a verificar que la actualización esté disponible y permitida.

Puedes registrar un selector de resultados de actividad con el contrato integrado ActivityResultContracts.StartIntentSenderForResult. Consulta la sección sobre cómo obtener la devolución de llamada para el estado de actualización.

Los pasos siguientes dependerán de si solicitas una actualización flexible o una actualización inmediata.

Cómo configurar una actualización con AppUpdateOptions

AppUpdateOptions contiene un campo AllowAssetPackDeletion que define si la actualización puede borrar los paquetes de recursos en caso de el dispositivo tenga almacenamiento limitado. Este campo se establece como false de forma predeterminada, pero puedes usar el método setAllowAssetPackDeletion() para establecerlo en true:

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build())

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build());

Cómo obtener una devolución de llamada sobre el estado de la actualización

Después de iniciar una actualización, la devolución de llamada del selector de resultados de la actividad registrada obtiene el resultado del diálogo de confirmación:

Kotlin

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult ->
    // handle callback
    if (result.resultCode != RESULT_OK) {
        log("Update flow failed! Result code: " + result.resultCode);
        // If the update is canceled or fails,
        // you can request to start the update again.
    }
}

Java

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // handle callback
            if (result.getResultCode() != RESULT_OK) {
                log("Update flow failed! Result code: " + result.getResultCode());
                // If the update is canceled or fails,
                // you can request to start the update again.
            }
        }
    });

Hay varios valores que puedes recibir de la devolución de llamada onActivityResult():

  • RESULT_OK: El usuario aceptó la actualización. En el caso de las actualizaciones inmediatas, es posible que no recibas esta devolución de llamada porque la actualización ya debería estar completa para el momento en que se le devuelve el control a la app.
  • RESULT_CANCELED: El usuario rechazó o canceló la actualización.
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED: Otro error impidió que el usuario proporcionara el consentimiento o que la actualización procediera.

Cómo manejar una actualización flexible

Cuando inicias una actualización flexible, el usuario primero ve un diálogo en el que se solicita su consentimiento. Si lo otorga, la descarga comienza en segundo plano y el usuario puede seguir interactuando con la app. En esta sección, se describe cómo supervisar y completar una actualización flexible integrada en la app.

Cómo supervisar el estado de una actualización flexible

Después de que comienza la descarga de una actualización flexible, la app necesita supervisar el estado de la actualización a fin de saber cuándo se puede instalar la actualización y mostrar el progreso en la IU de tu app.

Puedes supervisar el estado de una actualización en curso registrando un objeto de escucha para instalar actualizaciones de estado. También puedes proporcionar una barra de progreso en la IU de la app con el propósito de informar a los usuarios sobre el progreso de la descarga.

Kotlin

// Create a listener to track request state updates.
val listener = InstallStateUpdatedListener { state ->
    // (Optional) Provide a download progress bar.
    if (state.installStatus() == InstallStatus.DOWNLOADING) {
      val bytesDownloaded = state.bytesDownloaded()
      val totalBytesToDownload = state.totalBytesToDownload()
      // Show update progress bar.
    }
    // 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 -> {
  // (Optional) Provide a download progress bar.
  if (state.installStatus() == InstallStatus.DOWNLOADING) {
      long bytesDownloaded = state.bytesDownloaded();
      long totalBytesToDownload = state.totalBytesToDownload();
      // Implement progress bar.
  }
  // 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);

Cómo instalar una actualización flexible

Cuando detectes el estado InstallStatus.DOWNLOADED, deberás reiniciar la app para instalar la actualización.

A diferencia de las actualizaciones inmediatas, Google Play no activa automáticamente el reinicio de una app para una actualización flexible. Esto se debe a que, durante una actualización flexible, el usuario tiene la expectativa de seguir interactuando con la app hasta que decida que desea instalarla.

Te recomendamos que proporciones una notificación (o alguna otra indicación de la IU) para informarle al usuario que la actualización está lista para instalarse y solicitar una confirmación antes de reiniciar la app.

En el siguiente ejemplo se muestra la implementación de una barra de notificaciones de Material Design que solicita la confirmación del usuario para reiniciar la app:

Kotlin

val listener = { 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.
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

InstallStateUpdatedListener listener = 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();
}

Cuando llamas a appUpdateManager.completeUpdate() en primer plano, la plataforma muestra una IU en pantalla completa que reinicia la app en segundo plano. Una vez que la plataforma instala la actualización, la app se reinicia en su actividad principal.

Si, en cambio, llamas a completeUpdate() cuando la app está en segundo plano, la actualización se instala de manera silenciosa sin ocultar la IU del dispositivo.

Cuando el usuario lleve tu app al primer plano, verifica si tiene una actualización en espera para instalarse. Si tu app tiene una actualización en el estado DOWNLOADED, pídele al usuario que la instale. De lo contrario, los datos de la actualización seguirán ocupando el espacio de almacenamiento del dispositivo del usuario.

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

Cómo manejar una actualización inmediata

Cuando inicias una actualización inmediata y el usuario da su consentimiento para realizarla, Google Play muestra el progreso en la parte superior de la IU de tu app durante toda la actualización. Si el usuario cierra la app o la finaliza durante el proceso, la actualización debería continuar descargándose e instalándose en segundo plano sin que se requiera una nueva confirmación del usuario.

Sin embargo, cuando tu app regrese al primer plano, deberás confirmar que la actualización no está detenida en el estado UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS. Si la actualización se detiene en este estado, reanúdala:

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.
                appUpdateManager.startUpdateFlowForResult(
                  appUpdateInfo,
                  activityResultLauncher,
                  AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build())
            }
        }
}

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.
                appUpdateManager.startUpdateFlowForResult(
                  appUpdateInfo,
                  activityResultLauncher,
                  AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());
            }
          });
}

El flujo de actualización muestra un resultado como se describe en la documentación de referencia de startUpdateFlowForResult(). Específicamente, tu app debería poder controlar los casos en los que un usuario rechaza la actualización o cancela la descarga. Cuando el usuario realiza cualquiera de estas acciones, se cierra la IU de Google Play y tu app debe determinar la mejor manera de proceder.

Si es posible, permite que el usuario continúe interactuando sin la actualización y vuelve a solicitarla en otro momento. Si tu app no funciona sin la actualización, considera mostrar un mensaje informativo antes de reiniciar el flujo de actualización o pedirle al usuario que cierre la app. De esta manera, el usuario entiende que puede reiniciar la app cuando esté listo para instalar la actualización requerida.

Próximos pasos

Prueba las actualizaciones en la app para verificar que la integración funcione correctamente.