Resposta rápida: para pedir avaliação do seu app use a In-App Review API (ReviewManager) e nunca mais jogue o usuário na página da Play Store; para empurrar a versão nova use a In-App Updates API (AppUpdateManager) com fluxo flexível (download em segundo plano + reinício conveniente) ou imediato (tela de bloqueio, obrigatório para atualizações críticas). As duas APIs fazem parte do kit Play Core (com.google.android.play:review-ktx e com.google.android.play:app-update-ktx) e só funcionam em apps distribuídos pelo Google Play, já assinados com a chave de release ou em builds internos de teste. Não implemente review em um botão “Avalie-nos” que dispara toda hora — o Google controla as quotas e o diálogo simplesmente não aparece.
O que cada API resolve
As duas APIs cobrem os dois momentos mais delicados da vida de um app Android: a nota que o usuário dá e a versão que ele roda. Ambas foram criadas porque o anti-padrão clássico — abrir o link market://details?id=... — derruba o usuário do fluxo, tem conversão baixíssima e não permite atualizar nada.
| Necessidade | API | Componente | O que o usuário vê |
|---|---|---|---|
| Pedir avaliação ou nota | In-App Review | ReviewManager | Diálogo nativo da Play Store sobre o app |
| Atualizar com opção de adiamento | In-App Updates (flexível) | AppUpdateManager | Download em background + snack/dialog para reiniciar |
| Atualizar sem opção de adiamento | In-App Updates (imediato) | AppUpdateManager | Tela em tela cheia com barra de progresso |
| Testar fora do ciclo real | Play Core Testing | ReviewManager fake / FakeAppUpdateManager | Mesma UI em builds de teste |
Se seu app não é distribuído pelo Google Play, nenhuma das duas serve — nesse caso, atualização é backend service ou notificação, e review é link externo.
Dependências e configuração mínima
Adicione as bibliotecas do Play Core ao build.gradle.kts do módulo:
dependencies {
implementation("com.google.android.play:review-ktx:2.0.2")
implementation("com.google.android.play:app-update-ktx:2.1.0")
implementation("com.google.android.play:core-ktx:1.8.1")
}
Para o in-app update, não é necessário declarar INSTALL_REFERRER nem qualquer permissão especial: a AppUpdateManager depende apenas de o app estar publicado no Google Play e de o dispositivo ter uma versão recente dos serviços da loja.
Um detalhe que economiza horas de debugging: as duas APIs não funcionam com debug assinado com chave de debug em qualquer aparelho. Elas funcionam em:
- builds publicados (produção, open/closed test);
- Internal app sharing ou testes internos com assinatura do Play;
- builds locais assinados com a chave de release e testadas com as classes fake do Play Core Testing.
In-App Review: como pedir avaliação sem irritar
O princípio da API é simples: você pede, o Google decide. Você chama o fluxo no momento certo, mas o sistema avalia se mostra o diálogo — inclusive um limite interno de frequência (a documentação fala em algo próximo de uma vez ao mês por app, sem número garantido).
Fluxo básico
import com.google.android.play.core.review.ReviewManagerFactory
import com.google.android.play.core.review.testing.FakeReviewManager
class ReviewHelper(private val activity: Activity) {
private val manager = ReviewManagerFactory.create(activity)
fun pedirAvaliacao() {
val request = manager.requestReviewFlow()
request.addOnCompleteListener { task ->
if (task.isSuccessful) {
val reviewInfo = task.result
val flow = manager.launchReviewFlow(activity, reviewInfo)
flow.addOnCompleteListener {
// Fluxo terminou (aprovado OU não mostrado). Siga o app normalmente.
}
}
// Em falha: silêncio. Nunca trate "não mostrou" como erro do usuário.
}
}
}
Pontos que diferem do senso comum:
ReviewInfoé perecível. Só é válido por algumas horas. Peça orequestReviewFlowperto do momento de uso, não noonCreatedo app.- Não há callback dizendo se o diálogo apareceu. O
addOnCompleteListenerdispara nos dois casos. Você não pode saber a nota nem se a avaliação aconteceu. - Nunca incentive com recompensa. Dar moedas por avaliação viola a política da Play Store.
Quando acionar o pedido
Momentos que funcionam bem: após concluir uma tarefa importante (pedido entregue, treino finalizado, exportação concluída), após N sessões positivas, ou ao fechar um ticket de suporte resolvido. Momentos ruins: no primeiro launch, na abertura do app, no meio de um formulário, imediatamente após um crash. Se o app usa WorkManager para sincronizar dados, o fim da sincronização raramente é um bom gatilho — o usuário nem percebeu valor ainda.
Testando com FakeReviewManager
val manager = if (BuildConfig.DEBUG)
FakeReviewManager(context)
else
ReviewManagerFactory.create(context)
O fake sempre mostra o fluxo, o que permite validar a integração e o estado da UI em debug. Para validar o comportamento real de quota, use internal app sharing.
In-App Updates: flexível ou imediato?
A decisão central de qualquer integração. A pergunta que responde tudo: o app pode continuar rodando na versão antiga sem risco?
- Update flexível (
AppUpdateType.FLEXIBLE): o download acontece em background; quando termina, você mostra um snack ou botão para o usuário reiniciar quando quiser. Ideal para atualizações normais de features. - Update imediato (
AppUpdateType.IMMEDIATE): tela de tela cheia com barra de progresso; o usuário precisa atualizar para usar o app. Reserve para correções críticas — crash on launch, vulnerabilidade de segurança, quebra de API do backend.
Aplicar update imediato em toda release é o erro mais comum — e o mais punido em avaliações de usuários.
Preparando o AppUpdateManager
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.install.model.AppUpdateType
import com.google.android.play.core.install.model.UpdateAvailability
import com.google.android.play.core.ktx.AppUpdateResult
class UpdateHelper(private val activity: Activity) {
private val appUpdateManager = AppUpdateManagerFactory.create(activity)
fun checarAtualizacao(tipo: Int = AppUpdateType.FLEXIBLE) {
val request = appUpdateManager.appUpdateInfo
request.addOnSuccessListener { info: AppUpdateInfo ->
val disponivel = info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
val permitido = info.isUpdateTypeAllowed(tipo)
if (disponivel && permitido) {
iniciarFluxo(info, tipo)
}
}
}
private fun iniciarFluxo(info: AppUpdateInfo, tipo: Int) {
appUpdateManager.startUpdateFlowForResult(
info,
tipo,
activity,
REQUEST_CODE_UPDATE
)
}
companion object {
const val REQUEST_CODE_UPDATE = 7001
}
}
A partir das versões KTX recentes, existe também a API suspensa com coroutines, appUpdateManager.requestUpdateFlow(), que devolve um AppUpdateResult e se integra naturalmente a um ViewModel — mais idiomático em projetos Kotlin do que os listeners de Task.
Ouvindo o progresso no modo flexível
O update flexível só faz sentido se você retomar o estado depois. Registre um listener para saber quando o download termina:
import com.google.android.play.core.install.InstallState
import com.google.android.play.core.install.InstallStateUpdatedListener
import com.google.android.play.core.install.model.InstallStatus
private val listener = InstallStateUpdatedListener { state: InstallState ->
when (state.installStatus()) {
InstallStatus.DOWNLOADED -> mostrarSnackReiniciar()
InstallStatus.DOWNLOADING -> atualizarProgresso(state.bytesDownloaded(), state.totalBytesToDownload())
else -> Unit
}
}
override fun onResume() {
super.onResume()
appUpdateManager.registerListener(listener)
// App pode ter voltado de um update imediato concluído:
appUpdateManager.appUpdateInfo.addOnSuccessListener { info ->
if (info.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
appUpdateManager.startUpdateFlowForResult(
info,
AppUpdateType.IMMEDIATE,
this,
REQUEST_CODE_UPDATE
)
}
}
}
override fun onPause() {
super.onPause()
appUpdateManager.unregisterListener(listener)
}
private fun mostrarSnackReiniciar() {
Snackbar.make(
findViewById(R.id.main_content),
"Nova versão baixada. Reiniciar agora?",
Snackbar.LENGTH_INDEFINITE
).setAction("Reiniciar") {
appUpdateManager.completeUpdate()
}.show()
}
Dois comportamentos obrigatórios para produção:
DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS: se o processo morreu no meio de um update imediato (usuário fechou, sistema matou), retome o fluxo noonResume— o snippet acima cobre.completeUpdate()só no gesto do usuário no modo flexível. Chamar no background reinicia o app sem aviso e destrói confiança.
Testing com FakeAppUpdateManager
val fakeManager = FakeAppUpdateManager(context)
fakeManager.setUpdateAvailable(UpdateAvailability.UPDATE_AVAILABLE)
fakeManager.setUpdateAvailableVersionCode(42)
fakeManager.setClientVersionStalenessDays(3)
Isso permite testar os dois fluxos em CI e em testes de screenshot com Compose sem publicar nada.
Onde encaixar no ciclo de vida do app
Uma estratégia saudável para apps de produção:
| Evento | Ação |
|---|---|
| Primeiro launch | Nada. Nunca peça review nem update no onboarding |
| Fim de tarefa concluída (2ª+ vez) | Candidato a In-App Review |
| App em uso > X sessões na versão atual | Candidato a update flexível |
| Versão atual marcada como crítica (flag do backend) | Update imediato |
onActivityResult do update imediato | Retomar estado; usuário escolheu atualizar |
Marcar a versão como “crítica” deve vir de configuração de servidor, não hardcoded — assim você consegue suavizar a exigência se descobrir um bug na versão nova. Se o app já expõe um canal de push com FCM, combiná-lo com o update flexível aumenta muito a taxa de atualização sem recorrer ao bloqueio.
Erros comuns
- Chamar
launchReviewFlowdentro de um clique de “Avalie-nos 5 estrelas”. A política permite o botão, mas ele deve levar ao fluxo da API ou à listagem da Play — e não pode prometer recompensa. O melhor botão é o que leva à listagem; a API deve disparar sozinha em momentos naturais. - Requisitar o review flow no
onCreate. OReviewInfoexpira e você ancora a chamada no pior momento possível. - Tratar a ausência do diálogo como erro. Logar exceção, mostrar toast, pior: redirecionar para a Play Store “de fallback”. A quota é por design.
AppUpdateType.IMMEDIATEpara toda release. Bloquear usuário por features cosméticas gera reviews ruins e uninstall.- Não retomar
DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS. O usuário fica preso em um loop de tela branca no restart. - Esquecer
unregisterListener. Vazamento de listener e callbacks fantasma em fragments recriados. - Testar em build com chave de debug e achar que “não funciona”. É comportamento esperado: use as classes fake ou internal app sharing.
- Confiar que o update resolve compatibilidade de API antiga. O backend ainda precisa tolerar versões em defasagem por semanas — trate versão mínima com alertas de API deprecada no backend, não só no cliente.
Perguntas frequentes
In-App Review funciona fora do Google Play? Não. Em sideload, Amazon Appstore ou APK direto, a API falha silenciosamente. Nesses canais, o caminho é um link para a loja correspondente.
Posso saber se o usuário avaliou ou quantas estrelas deu? Não. A API não retorna nota nem confirmação — por privacidade e por design anti-manipulação.
Quantas vezes o diálogo de review aparece? O Google não publica o número exato; a orientação oficial é tratar como “esporádico” e nunca depender dele para o único pedido de feedback. Para feedback sempre disponível, abra um canal próprio (ex.: formulário interno ou Crashlytics + diagnóstico de ANR para ouvir falhas antes de pedir nota).
Update flexível baixa dados móveis? Sim, o download usa a conexão disponível. Não há controle fino de rede exposto pela API — se isso importa ao seu público, espere Wi-Fi verificando isActiveNetworkMetered antes de iniciar o fluxo.
Preciso dos dois fluxos no mesmo app? Normalmente sim: flexível como padrão, imediato acionado por flag de versão crítica. Só o imediato só faz sentido em apps com requisito de segurança rígido (banco, por exemplo, como os retratados em empresas que usam Kotlin no Brasil).
Funciona em Wear OS? Não. Dispositivos que não suportam a API devolvem falha na requestReviewFlow/appUpdateInfo — trate como no-op. Para a loja no wearable, veja o guia de Wear OS com Compose.
Conclusão
In-App Review e In-App Updates fecham o ciclo de relacionamento com o usuário dentro do próprio app: a nota pedida no momento certo, sem tirar ninguém do fluxo, e a versão nova chegando sem depender da boa vontade diante de um link. A receita é a mesma nas duas: pedir no contexto natural, aceitar que o Google controla a quota, retomar fluxos interrompidos e reservar o update imediato para quando a versão antiga realmente não pode rodar.
Como próximo passo no ciclo de distribuição, avalie Play Feature Delivery para módulos dinâmicos para encolher o APK, e Play Billing para assinaturas e compras se o app monetiza dentro da loja — junto com Integrity, Review e Updates, esse é o kit completo de Play Core para um app Kotlin maduro em 2026.