Compatibilidade com atualizações no app (Kotlin ou Java)

Este guia descreve como oferecer compatibilidade com atualizações no app usando Kotlin ou Java. Há guias separados para casos em que a implementação usa o código nativo (C/C++) e os casos em que ela usa o Unity.

Verificar a disponibilidade de atualizações

Antes de solicitar uma atualização, confira se há uma disponível para seu app, usando 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.
    }
});

A instância AppUpdateInfo retornada contém o status de disponibilidade da atualização. Dependendo do status da atualização, ela também contém o seguinte:

  • Se uma atualização estiver disponível e ela for permitida, a instância também terá uma intent para iniciá-la.
  • Se uma atualização no app já estiver em andamento, a instância também informará o status dela.

Conferir inatividade de atualização

Além de conferir se uma atualização está disponível, também é possível saber quanto tempo se passou desde que o usuário foi notificado pela última vez sobre uma atualização pela Play Store. Isso pode ajudar você a decidir se deve iniciar uma atualização flexível ou uma imediata. Por exemplo, você pode esperar alguns dias antes de notificar o usuário sobre uma atualização flexível e mais alguns antes de exigir uma imediata.

Use clientVersionStalenessDays() para verificar o número de dias desde que a atualização foi disponibilizada na 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 {
    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.
    }
});

Verificar prioridade de atualização

A API Google Play Developer permite que você defina a prioridade de cada atualização. Isso permite que o app decida como recomendar uma atualização para o usuário. Por exemplo, considere a seguinte estratégia para definir a prioridade de atualização:

  • Pequenas melhorias na IU: atualização de baixa prioridade. Não exigem a atualização flexível nem a imediata. Só atualize quando o usuário não estiver interagindo com o app.
  • Melhorias de desempenho: atualização de prioridade média. Exigem uma atualização flexível.
  • Atualização crítica de segurança: atualização de alta prioridade. Exige uma atualização imediata.

Para determinar a prioridade, o Google Play usa um valor inteiro entre 0 e 5, sendo 0 o padrão e 5 a prioridade mais alta. Para definir a prioridade de uma atualização, use o campo inAppUpdatePriority em Edits.tracks.releases na API Google Play Developer. Todas as versões recém-adicionadas são consideradas como tendo a mesma prioridade da versão lançada. A prioridade só pode ser definida ao lançar uma nova versão e não pode ser mudada posteriormente.

Defina a prioridade usando a API Google Play Developer, conforme descrito na documentação da API Google Play Developer. A prioridade de atualização no app precisa ser especificada no recurso Edit.tracks transmitido no método Edit.tracks: update. O exemplo a seguir demonstra o lançamento de um APK com o código de versão 88 e inAppUpdatePriority 5:

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

No código do app, é possível conferir o nível de prioridade de uma determinada atualização usando o método updatePriority(). A prioridade retornada considera a inAppUpdatePriority para todos os códigos de versão do app entre a versão instalada e a mais recente disponível.

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

Iniciar uma atualização

Depois de confirmar que há uma atualização disponível, solicite-a atualização usando AppUpdateManager.startUpdateFlowForResult():

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

Cada instância AppUpdateInfo só pode ser usada uma vez para iniciar uma atualização. Para repetir a atualização em caso de falha, solicite uma nova AppUpdateInfo e confira novamente se a atualização está disponível e é permitida.

As próximas etapas dependem do tipo de atualização que você está solicitando: flexível ou imediata.

Configurar uma atualização com AppUpdateOptions

Também é possível criar e transmitir um objeto AppUpdateOptions em vez de um tipo de fluxo de atualização explícito. Além do campo appUpdateType, os objetos AppUpdateOptions também contêm um campo AllowAssetPackDeletion, que define se a atualização tem permissão para limpar pacotes de recursos no caso de espaço de armazenamento limitado no dispositivo. Esse campo é definido como false por padrão, mas é possível usar o método setAllowAssetPackDeletion() para defini-lo como true:

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // The current activity making the update request.
    this,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build(),
    // 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,
    // The current activity making the update request.
    this,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build(),
    // Include a request code to later monitor this update request.
    MY_REQUEST_CODE);

Receber um callback para o status da atualização

Depois de iniciar uma atualização, é possível usar um callback onActivityResult() para lidar com uma falha ou com o cancelamento do processo:

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == MY_REQUEST_CODE) {
        if (resultCode != RESULT_OK) {
            Log.e("MY_APP", "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.
    }
  }
}

Você pode receber diferentes valores do callback onActivityResult():

  • RESULT_OK: o usuário aceitou a atualização. No caso de atualizações imediatas, talvez você não receba esse callback, já que a atualização provavelmente já terá sido concluída quando o controle de tempo for devolvido ao app.
  • RESULT_CANCELED: o usuário negou ou cancelou a atualização.
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED: outro erro impediu que o usuário permitisse a atualização ou que ela continuasse.

Processar uma atualização flexível

Quando você inicia uma atualização flexível, uma caixa de diálogo aparece para solicitar o consentimento do usuário. Se ele consentir, o download será iniciado em segundo plano e o usuário poderá continuar interagindo com o app. Esta seção descreve como monitorar e concluir uma atualização flexível no app.

Monitorar o estado da atualização flexível

Depois que o download é iniciado para uma atualização flexível, seu app precisa monitorar o estado da atualização para saber quando ela pode ser instalada e para exibir o progresso na IU do app.

Você pode monitorar o estado de uma atualização em andamento registrando um listener para instalar as atualizações de status. Você também pode fornecer uma barra de progresso na IU do app para informar os usuários sobre o progresso do download.

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

Instalar uma atualização flexível

Quando você detecta o estado InstallStatus.DOWNLOADED, é necessário reiniciar o app para instalar a atualização.

Ao contrário das atualizações imediatas, o Google Play não aciona automaticamente a reinicialização de um app para uma atualização flexível. Isso ocorre porque, durante uma atualização flexível, o usuário pretende continuar interagindo com o app até decidir que quer instalar a atualização.

Recomendamos que você apresente uma notificação ou alguma outra indicação na IU que informe ao usuário que a atualização está pronta para ser instalada e solicite a confirmação para reiniciar o app.

O exemplo a seguir demonstra a implementação de uma snackbar com Material Design (link em inglês) que solicita a confirmação do usuário para reiniciar o 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();
}

Quando você chama o appUpdateManager.completeUpdate() em primeiro plano, a plataforma exibe uma IU em tela cheia que reinicia o app em segundo. Depois que a plataforma instala a atualização, o app é reiniciado para a atividade principal.

Se, em vez disso, você chamar completeUpdate() quando o app estiver em segundo plano, a atualização será instalada silenciosamente, sem ocultar a IU do dispositivo.

Sempre que o usuário colocar seu app em primeiro plano, confira se há alguma atualização esperando para ser instalada. Se o app tiver uma atualização no estado DOWNLOADED, solicite que o usuário a instale. Caso contrário, os dados de atualização continuarão ocupando o armazenamento do dispositivo.

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

Gerenciar uma atualização imediata

Quando você inicia uma atualização imediata e o usuário consente em começá-la, o Google Play exibe o progresso da atualização na parte superior da IU do app durante todo o processo. Se o usuário fechar ou encerrar o app durante a atualização, o download e a instalação dela continuarão em segundo plano, sem necessidade de outra confirmação.

No entanto, quando o app retornar para o primeiro plano, você precisará confirmar se a atualização não está parada no estado UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS. Se estiver, retome a atualização:

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,
                    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.
                appUpdateManager.startUpdateFlowForResult(
                    appUpdateInfo,
                    IMMEDIATE,
                    this,
                    MY_REQUEST_CODE);
            }
          });
}

O fluxo de atualização retorna um resultado, conforme descrito na documentação de referência para startUpdateFlowForResult(). Em particular, seu app precisa conseguir lidar com casos em que um usuário recusa a atualização ou cancela o download. Quando o usuário realiza uma dessas ações, a IU do Google Play é fechada. Seu app precisa determinar a melhor maneira de prosseguir.

Se possível, permita que o usuário continue sem a atualização e solicite-a novamente mais tarde. Caso o app não funcione sem a atualização, recomendamos exibir uma mensagem informativa antes de reiniciar o fluxo de atualização ou solicitar que o usuário feche o app. Dessa forma, o usuário entenderá que pode reiniciar o app quando estiver pronto para instalar a atualização necessária.

A seguir

Testar as atualizações no app para verificar se a integração está funcionando corretamente.