Suporte a 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.

Configurar seu ambiente de desenvolvimento

A biblioteca Play In-App Update faz parte das Bibliotecas Google Play Core. Inclua a dependência do Gradle abaixo para integrar a biblioteca Play In-App Update do Google 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")
    ...
}

Conferir se há atualizações disponíveis

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

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 performance: atualização de prioridade média. Exigem uma atualização flexível.
  • Atualização crítica de segurança: é de alta prioridade e precisa ser feita de forma 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 app 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, independente da faixa de lançamento. Por exemplo, considere este cenário:

  • Você lançou a versão 1 em uma faixa de produção sem prioridade.
  • Você lançou a versão 2 em uma faixa de teste interno com prioridade 5.
  • Você lançou a versão 3 em uma faixa de produção sem prioridade.

Quando os usuários da versão de produção são atualizados da versão 1 para a 3, eles recebem a prioridade 5, mesmo que a versão 2 tenha sido publicada em uma faixa 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.
    }
});

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

Você pode registrar um inicializador de resultados de atividades usando o contrato ActivityResultContracts.StartIntentSenderForResult integrado. Confira a seção sobre como receber o status de atualização do callback.

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

Configurar uma atualização com AppUpdateOptions

AppUpdateOptions contém um campo AllowAssetPackDeletion que define se a atualização pode limpar os pacotes de recursos caso o armazenamento do dispositivo esteja limitado. 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,
    // 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());

Receber um callback para o status da atualização

Depois de iniciar uma atualização, o callback do inicializador de resultados de atividades registrado recebe o resultado da caixa de diálogo de confirmação:

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

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 aplicativo. 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 interface 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ê chama completeUpdate() quando o app estiver em segundo plano, a atualização é instalada silenciosamente, sem ocultar a interface 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,
                  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());
            }
          });
}

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.

Próximas etapas

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