Resposta rápida: o Predictive Back é o gesto de voltar do Android que mostra, em tempo real, para onde a tela vai ao arrastar a partir da borda. Em apps Kotlin com Jetpack Compose, o caminho moderno é: (1) declarar android:enableOnBackInvokedCallback="true" no Manifest; (2) migrar interceptações de onBackPressed() para OnBackPressedDispatcher, BackHandler ou APIs de Navigation; (3) deixar o sistema animar a transição padrão ou fornecer animações customizadas só quando o app realmente controla o destino. Sem o callback moderno, o gesto fica “cego” ou o app captura o back de forma legada e perde a prévia. Predictive Back melhora UX e consistência com o sistema; ele não substitui Navigation Compose nem a decisão de produto sobre o que acontece ao voltar.
Desde o Android 13 o sistema vem empurrando um modelo de voltar previsível: o usuário arrasta, vê a tela de destino (ou o launcher) e só confirma o gesto no soltar. Em 2026 isso já é expectativa de qualidade em apps que usam gesture navigation. O problema para times Kotlin é histórico: muitos códigos ainda interceptam o botão/gesto com APIs antigas, finish() espalhado, ou BackHandler sem estratégia clara de pilha.
Este guia explica o que muda na plataforma, como habilitar o recurso, como encaixar Compose e Navigation, quando animar customizado, como testar e quais erros matam a prévia. Se você ainda está modernizando a navegação, leia em paralelo o guia de Navigation 3 no Compose e o de edge-to-edge no Android 16.
O que é Predictive Back?
Predictive Back é a experiência de pré-visualização do destino enquanto o gesto de voltar está em andamento. Em vez de um “pop” instantâneo no fim do gesto, o sistema interpola a tela atual em direção à tela anterior (ou ao home) conforme o progresso do arraste.
Do ponto de vista de API, a peça central é o OnBackInvokedCallback (e o dispatcher associado). Apps e bibliotecas registram callbacks com prioridade; o sistema consulta o callback no topo da pilha para decidir se o back é consumido pelo app ou se continua para a atividade/sistema.
Isso é diferente de apenas “tratar o botão voltar”:
| Abordagem | O que o usuário vê | Risco |
|---|---|---|
onBackPressed() legado | Sem prévia confiável | Quebra Predictive Back e gera avisos |
OnBackPressedDispatcher / BackHandler | App consome o evento de forma moderna | Sem animação custom, ainda pode ter prévia do sistema |
| Callback com progresso + animação | Prévia custom alinhada ao destino real | Mais código e cuidado com cancelamento |
Nada registrado + enableOnBackInvokedCallback | Prévia do sistema para Activity/Fragment/Compose padrão | Pode “pular” lógica de negócio se a pilha estiver errada |
A regra prática: migrar a interceptação é obrigatório para conviver com o gesto; animação custom só vale quando o destino não é óbvio ou a marca precisa de continuidade visual forte.
Por que isso importa em apps Kotlin de 2026?
Três motivos concretos para times Android no Brasil:
- Consistência com o SO. Usuários de gesture navigation esperam a mesma linguagem visual de apps do sistema e de grandes produtos.
- Menos surpresa na navegação. Prévia reduz “voltei sem querer” e “não sei para onde vou”, especialmente em fluxos longos de checkout, onboarding e formulários.
- Pressão de qualidade em review e performance percibida. Um back abrupto em app edge-to-edge com Material 3 parece datado ao lado de animações de Compose.
Predictive Back também força higiene de arquitetura: se o app só funciona porque alguém chama finish() em cinco lugares, a migração vai doer — e isso é um sinal útil, não um bug da plataforma.
Habilitando no Manifest
No AndroidManifest.xml da application (ou da activity principal, se o projeto isolar por tela):
<application
android:enableOnBackInvokedCallback="true"
... >
Esse flag informa ao sistema que o app está preparado para o modelo de callback moderno. Sem ele, o Android pode manter comportamento de compatibilidade e a prévia não aparece como você espera.
Checklist mínimo depois de ligar o flag:
- busque
onBackPressed()eOnBackPressedlegados no módulo app; - confira bibliotecas de navegação, WebView, players e SDKs de pagamento;
- valide fluxos com teclado aberto, bottom sheet e dialog;
- rode o gesto em dispositivo real com navigation por gestos (emulador ajuda, mas o feeling do arraste muda por OEM).
Compose: BackHandler do jeito certo
No Compose, a API cotidiana é BackHandler. Ela se registra no OnBackPressedDispatcher da Activity hospedeira enquanto o composition está ativo.
@Composable
fun DetalhePedidoScreen(
pedidoId: String,
temAlteracoesNaoSalvas: Boolean,
onConfirmarSaida: () -> Unit,
onVoltar: () -> Unit,
) {
var mostrarDialogo by remember { mutableStateOf(false) }
BackHandler(enabled = true) {
if (temAlteracoesNaoSalvas) {
mostrarDialogo = true
} else {
onVoltar()
}
}
if (mostrarDialogo) {
ConfirmarSaidaDialog(
onDescartar = {
mostrarDialogo = false
onConfirmarSaida()
},
onContinuarEditando = { mostrarDialogo = false },
)
}
// conteúdo da tela...
}
Pontos que evitam bug em produção:
enableddeve refletir o estado real. Se o dialog já está aberto, muitas vezes você quer outroBackHandlerno dialog para fechar o dialog em vez de sair da tela.- Não faça trabalho pesado no callback. Persistência, rede e analytics disparam depois, em coroutine/ViewModel.
- Um único dono da decisão de back por nível de UI. Bottom sheet, scaffold e nav host competindo pelo mesmo evento geram “preciso apertar duas vezes”.
Exemplo com prioridade de camadas:
@Composable
fun TelaComSheet(
sheetAberto: Boolean,
onFecharSheet: () -> Unit,
onVoltarTela: () -> Unit,
) {
BackHandler(enabled = sheetAberto) {
onFecharSheet()
}
BackHandler(enabled = !sheetAberto) {
onVoltarTela()
}
}
A composition mais específica (sheet) deve ganhar quando estiver visível. Isso é o equivalente Compose de empilhar callbacks no dispatcher.
Navigation Compose e a pilha de voltar
Na maior parte dos apps, o “destino do Predictive Back” é simplesmente a entrada anterior da back stack do NavController. O trabalho do time é garantir que a pilha representa a história real do usuário.
Boas práticas:
- prefira
navController.popBackStack()/ APIs do Navigation em vez deactivity.finish()no meio do grafo; - use rotas claras e, se estiver em migração, avalie o modelo do Navigation 3;
- deep links devem empilhar um histórico sensato — senão o gesto volta para o launcher sem passar pelo hub do app (combine com App Links e deep links);
- telas de login/splash raramente devem permanecer na stack após autenticação.
Esboço típico:
@Composable
fun AppNavHost(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = "home",
) {
composable("home") {
HomeScreen(
onAbrirDetalhe = { id ->
navController.navigate("detalhe/$id")
},
)
}
composable("detalhe/{id}") { entry ->
val id = entry.arguments?.getString("id").orEmpty()
DetalheScreen(
id = id,
onVoltar = { navController.popBackStack() },
)
}
}
}
Se cada tela chama BackHandler { navController.popBackStack() } sem necessidade, você só duplica o que o Navigation já faz. Use BackHandler para política de produto (descartar rascunho, fechar busca, sair de modo seleção), não para reimplementar a pilha.
Progresso do gesto e animações customizadas
Quando o app precisa de uma transição própria — por exemplo, um detalhe que encolhe de volta para o card da lista — você consome o progresso do gesto em vez de só o “evento final”.
A ideia geral:
- registrar um callback que recebe atualizações de progresso (0f → 1f);
- mapear progresso para animação de saída da tela atual;
- no cancelamento (usuário desistiu do gesto), reverter;
- no commit, efetivar
popBackStack()/finish().
Em Compose, isso costuma combinar Animatable, graphicsLayer e APIs de back progress disponíveis na versão do AndroidX Activity/Compose que o projeto usa. Mantenha a animação barata: scale, alpha e translation são suficientes na maioria dos casos; blur pesado e recomposição de lista inteira destroem a fluidez do gesto.
Princípios de design:
- a prévia deve sugerir o destino real, não uma animação genérica bonita mas mentirosa;
- se o destino é o launcher, muitas vezes a animação do sistema é melhor que a custom;
- cancele listeners ao sair da composition para não vazar callback;
- respeite
Motionreduzida / preferências de acessibilidade quando o app já trata isso em outras animações Compose.
Interações que mais quebram Predictive Back
1. WebView
WebView tem histórico próprio. Se o usuário navegou três páginas internas, o back deve primeiro webView.canGoBack() / goBack(), e só depois sair da tela. Implemente isso com BackHandler condicionado ao histórico do WebView; senão o gesto fecha a Activity no meio da leitura.
2. Multiseleção e modo contextual
Em listas com seleção, o primeiro back limpa a seleção; o segundo volta na navegação. Modele isso com estado (selectedIds.isNotEmpty()) no enabled do BackHandler.
3. Teclado e foco
Fechar o teclado no back é UX comum, mas dispute menos prioridade que sheet e dialog. Ordem sugerida: dialog → sheet → modo seleção → teclado → pop de navegação.
4. Players e fullscreen
Players de mídia e câmera em fullscreen precisam decidir se o back sai do fullscreen ou da tela. Documente a regra; não deixe cada OEM parecer um bug diferente. Para pipeline de mídia mais amplo, o ecossistema atual gira em torno de Media3 — um tema irmão que vale guia dedicado depois de estabilizar a navegação.
5. SDKs legados
Alguns SDKs de checkout ou chat ainda usam interceptação antiga. Teste o fluxo completo com o flag ligado; se o SDK engolir o back sem prévia, isole a Activity do SDK ou atualize a versão.
Testes manuais e automatizados
Roteiro manual mínimo (gesture navigation ligado):
- abrir lista → detalhe → arrastar back devagar e cancelar no meio;
- mesmo fluxo até o commit (soltar com progresso alto);
- detalhe com alterações não salvas + dialog de confirmação;
- bottom sheet aberto sobre a tela;
- deep link direto no detalhe e gesto de voltar;
- rotação / multi-window se o app suportar;
- comparar API 33+ em emulador e um aparelho físico recente.
Em UI tests, valide a política (dialog aparece, sheet fecha, stack popa) mais do que o frame-a-frame da prévia do sistema. Ferramentas de screenshot e fluxos end-to-end do ecossistema Compose ajudam a regressar navegação; veja também o material de testes de screenshot no Compose e o guia de testes Android com Maestro.
Para performance de abertura e animação de entrada/saída da Activity, combine com Baseline Profiles e Macrobenchmark — Predictive Back não salva um cold start pesado com trabalho demais no App Startup.
Erros comuns
- Ligar o flag e manter
onBackPressed(). O app entra em modo híbrido confuso. Migre de verdade. BackHandlerem toda tela só para logar analytics. Prefira um listener central ou eventos de Navigation; callback demais complica prioridade.- Animação custom que não corresponde ao destino. O usuário vê a home na prévia e cai no login — confiança zero.
- Esquecer deep link / task affinity. Predictive Back expõe pilha mal montada.
- Tratar back como “cancelar rede”. Cancele jobs no
ViewModel.onCleared/ escopo de tela; o gesto não é lifecycle completo. - Testar só com botão virtual de 3 botões. A prévia é sobre gesto; valide com gesture navigation.
- Ignorar acessibilidade. Foco, leitores de tela e ações de acessibilidade ainda precisam de caminho de voltar claro — o gesto visual não cobre tudo. Há guia específico de acessibilidade no Compose.
Checklist de migração para o time
Use este roteiro em PR de modernização:
enableOnBackInvokedCallback=trueno Manifest.- Grep de
onBackPressed,OnKeyListenerde KEYCODE_BACK efinish()suspeitos. - Inventário de telas que interceptam back (form, player, WebView, seleção).
- Padronizar
BackHandler+ estado de UI; remover duplicatas. - Revisar grafo Navigation e deep links.
- Decidir onde a animação do sistema basta e onde custom agrega.
- Testar gesto lento, cancelamento e commit em API 34+.
- Documentar a ordem de prioridade (dialog → sheet → seleção → nav).
- Monitorar crashes/ANRs em fluxos de saída após o release.
Quando NÃO customizar
Nem todo app precisa de animação de marca no back. Evite customização se:
- o grafo é linear e o sistema já pré-visualiza corretamente;
- o time não tem budget para manter progresso + cancelamento;
- a tela é majoritariamente texto/forms sem hero compartilhado com a lista;
- vocês ainda estão migrando de XML/Fragments e a pilha não está estável.
Nesses casos, habilitar o callback moderno + limpar interceptações legadas já entrega a maior parte do valor. Custom é polish de produto, não requisito de compilação.
Predictive Back e arquitetura MVVM
O callback de back é evento de UI. A decisão “posso sair?” pode viver no ViewModel (uiState.isDirty), mas a animação e o registro do callback ficam na camada Compose. Evite chamar Navigation diretamente de use cases.
Fluxo saudável:
- UI recebe gesto /
BackHandler; - UI consulta estado (
isDirty,isSelectionMode); - se precisar confirmar, UI mostra dialog;
- ao confirmar, UI chama
viewModel.descartar()e depoisonVoltar()do NavHost; - ViewModel cancela jobs da tela no clear do escopo.
Isso alinha com o guia de arquitetura MVVM e evita ViewModel “sabendo” de NavController.
Perguntas frequentes
Predictive Back funciona em versões antigas do Android?
A experiência completa de prévia depende da versão do sistema e das bibliotecas AndroidX. Em APIs antigas o back continua funcionando de forma clássica. Desenvolva com degradação graciosa: a lógica de BackHandler permanece válida; só a animação preditiva some.
Preciso de Predictive Back para publicar na Play?
Não é um bloqueio binário universal de publicação, mas é parte da qualidade moderna de navegação. Apps que ignoram o modelo novo tendem a parecer inconsistentes em aparelhos com gesture navigation — a maioria dos usuários Android no Brasil.
BackHandler atrapalha o Predictive Back?
Não, se usado para consumir o evento de forma legítima (fechar sheet, confirmar saída). Ele atrapalha quando engole o back sem feedback visual e sem necessidade, impedindo o sistema de pré-visualizar o destino real.
Como lidar com múltiplas Activities?
Cada Activity tem sua própria política. Se o app ainda usa várias Activities, o gesto pode fechar a Activity atual e revelar a anterior do task. Avalie se o fluxo deveria ser um único grafo Compose; menos Activities = menos surpresas no back.
E apps com Compose Multiplatform?
Predictive Back é conceito Android. Em projetos KMP, isole o registro do callback no source set Android e mantenha a regra de navegação compartilhada o mais agnóstica possível. O guia de Compose Multiplatform ajuda a separar UI compartilhada de integrações de plataforma.
Conclusão e próximos passos
Predictive Back é menos sobre “uma API nova de animação” e mais sobre respeitar o gesto de voltar como conversa com o sistema: o usuário pergunta “para onde vou?” e o app responde com honestidade visual e de pilha. Em Kotlin + Compose, o pacote vencedor em 2026 é Manifest com enableOnBackInvokedCallback, interceptações só via dispatcher/BackHandler, Navigation com stack verdadeira e animação custom apenas onde o produto ganha.
Próximos passos sugeridos:
- ligue o flag e faça o grep de código legado hoje;
- padronize a ordem dialog → sheet → seleção → nav;
- revise deep links e task stack;
- meça jank nas telas com mais saída por gesto;
- aprofunde navegação com Navigation Compose e Navigation 3.
Se a meta do time é vaga Android no Brasil, domínio de navegação moderna e UX de sistema pesa em entrevista tanto quanto saber Room ou coroutines — combine este tema com o roadmap de dev Android e as vagas Kotlin.