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”:

AbordagemO que o usuário vêRisco
onBackPressed() legadoSem prévia confiávelQuebra Predictive Back e gera avisos
OnBackPressedDispatcher / BackHandlerApp consome o evento de forma modernaSem animação custom, ainda pode ter prévia do sistema
Callback com progresso + animaçãoPrévia custom alinhada ao destino realMais código e cuidado com cancelamento
Nada registrado + enableOnBackInvokedCallbackPrévia do sistema para Activity/Fragment/Compose padrãoPode “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:

  1. Consistência com o SO. Usuários de gesture navigation esperam a mesma linguagem visual de apps do sistema e de grandes produtos.
  2. 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.
  3. 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() e OnBackPressed legados 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:

  • enabled deve refletir o estado real. Se o dialog já está aberto, muitas vezes você quer outro BackHandler no 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.

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 de activity.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:

  1. registrar um callback que recebe atualizações de progresso (0f → 1f);
  2. mapear progresso para animação de saída da tela atual;
  3. no cancelamento (usuário desistiu do gesto), reverter;
  4. 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 Motion reduzida / 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):

  1. abrir lista → detalhe → arrastar back devagar e cancelar no meio;
  2. mesmo fluxo até o commit (soltar com progresso alto);
  3. detalhe com alterações não salvas + dialog de confirmação;
  4. bottom sheet aberto sobre a tela;
  5. deep link direto no detalhe e gesto de voltar;
  6. rotação / multi-window se o app suportar;
  7. 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

  1. Ligar o flag e manter onBackPressed(). O app entra em modo híbrido confuso. Migre de verdade.
  2. BackHandler em toda tela só para logar analytics. Prefira um listener central ou eventos de Navigation; callback demais complica prioridade.
  3. 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.
  4. Esquecer deep link / task affinity. Predictive Back expõe pilha mal montada.
  5. Tratar back como “cancelar rede”. Cancele jobs no ViewModel.onCleared / escopo de tela; o gesto não é lifecycle completo.
  6. Testar só com botão virtual de 3 botões. A prévia é sobre gesto; valide com gesture navigation.
  7. 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:

  1. enableOnBackInvokedCallback=true no Manifest.
  2. Grep de onBackPressed, OnKeyListener de KEYCODE_BACK e finish() suspeitos.
  3. Inventário de telas que interceptam back (form, player, WebView, seleção).
  4. Padronizar BackHandler + estado de UI; remover duplicatas.
  5. Revisar grafo Navigation e deep links.
  6. Decidir onde a animação do sistema basta e onde custom agrega.
  7. Testar gesto lento, cancelamento e commit em API 34+.
  8. Documentar a ordem de prioridade (dialog → sheet → seleção → nav).
  9. 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:

  1. UI recebe gesto / BackHandler;
  2. UI consulta estado (isDirty, isSelectionMode);
  3. se precisar confirmar, UI mostra dialog;
  4. ao confirmar, UI chama viewModel.descartar() e depois onVoltar() do NavHost;
  5. 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:

  1. ligue o flag e faça o grep de código legado hoje;
  2. padronize a ordem dialog → sheet → seleção → nav;
  3. revise deep links e task stack;
  4. meça jank nas telas com mais saída por gesto;
  5. 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.