Fornecer navegação personalizada no sentido contrário

Os usuários navegam para trás nas telas usando a navegação de retorno. A maioria dos dispositivos Android tem um botão "Voltar" (físico, de software ou baseado em gestos). Normalmente, não é necessário adicionar um botão "Voltar" ao seu app. No entanto, os dispositivos Android Automotive OS (AAOS) no modo de compatibilidade usam um botão "Voltar" do sistema. Isso processa a navegação, então não é necessário adicionar seu próprio botão. Para mais detalhes, consulte Modo de compatibilidade do AAOS.

O Android mantém uma backstack de destinos à medida que o usuário navega no seu app. Isso normalmente permite que o Android navegue adequadamente para destinos anteriores quando o botão "Voltar" é pressionado. No entanto, há casos em que seu app precisa implementar um comportamento de retorno próprio para fornecer a melhor experiência do usuário possível. Por exemplo, ao usar uma WebView, convém modificar o comportamento padrão do botão "Voltar" para permitir que o usuário navegue de volta pelo histórico de navegação da Web em vez das telas anteriores no seu app.

Implementar a navegação de retorno personalizada no Compose

No Jetpack Compose, é possível processar a navegação de retorno personalizada usando o BackHandler elemento combinável.

Ao usar Navigation Compose, normalmente você usa NavController.navigateUp() ou NavController.popBackStack() para navegar até a tela anterior na backstack. No entanto, BackHandler é útil para casos em que você quer implementar um comportamento personalizado quando o usuário pressiona o botão "Voltar" do sistema ou usa o gesto de retorno. Por exemplo, se você estiver mostrando um WebView no seu app, talvez queira permitir que os usuários naveguem de volta pelo histórico de navegação quando pressionarem o botão "Voltar" do sistema.

Uso básico

Confira um exemplo de como interceptar o evento de retorno para controlar o estado personalizado (como recolher um painel):

@Composable
fun MyScreen() {
    var isExpanded by remember { mutableStateOf(false) }

    // Intercept the back button or gesture only when isExpanded is true
    BackHandler(enabled = isExpanded) {
        // Collapsing the content disables this handler, so it does not
        // receive the next back event.
        isExpanded = false
    }

    if (isExpanded) {
        Text("Expanded content. Go back to collapse it.")
    } else {
        Button(onClick = { isExpanded = true }) {
            Text("Expand")
        }
    }
}

Interceptação da árvore de elementos combináveis

Se você tiver vários elementos combináveis BackHandler ativados em diferentes níveis da árvore de elementos combináveis, apenas o mais interno interceptará o evento de retorno.

Implementar a navegação de retorno personalizada com visualizações

ComponentActivity, a classe de base para FragmentActivity e AppCompatActivity, permite controlar o comportamento do botão "Voltar" usando o OnBackPressedDispatcher, que pode ser recuperado chamando getOnBackPressedDispatcher().

O OnBackPressedDispatcher controla como os eventos do botão "Voltar" são enviados para um ou mais objetos OnBackPressedCallback. O construtor de OnBackPressedCallback usa um booleano para o estado ativado inicial. Somente quando um callback está ativado, por exemplo, quando isEnabled() retorna true, o dispatcher chama handleOnBackPressed() do callback para processar o evento do botão "Voltar". Você pode alterar o estado ativado chamando setEnabled().

Os callbacks são adicionados usando os métodos addCallback. Use o addCallback() método, que usa um LifecycleOwner. Dessa forma, o OnBackPressedCallback só é adicionado quando o LifecycleOwner é Lifecycle.State.STARTED. A atividade também remove os callbacks registrados quando o LifecycleOwner associado é destruído, o que evita vazamentos de memória e o torna adequado para uso em fragmentos ou outros proprietários de ciclo de vida que têm um tempo de vida mais curto do que a atividade.

Veja um exemplo de implementação de callback:

class MyFragment : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // This callback will only be called when MyFragment is at least Started.
        val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
            // Handle the back button event
        }

        // The callback can be enabled or disabled here or in the lambda
    }
    ...
}

É possível fornecer vários callbacks usando addCallback(). Ao fazer isso, os callbacks são invocados na ordem inversa em que são adicionados. O callback adicionado por último é o primeiro a ter a chance de processar o evento do botão "Voltar". Por exemplo, se você adicionou três callbacks chamados one, two e three em ordem, eles seriam invocados na ordem de three, two e one, respectivamente.

Os callbacks seguem o padrão Cadeia de responsabilidade. Cada callback na cadeia é invocado apenas se o callback anterior não estiver ativado. Isso significa que, no exemplo anterior, o callback two seria invocado apenas se o callback three não estivesse ativado. O callback one só seria invocado se o callback two não estivesse ativado, e esse padrão continua para outros callbacks.

Quando adicionado usando addCallback(), o callback não é adicionado à cadeia de responsabilidade até que o LifecycleOwner entre no estado Lifecycle.State.STARTED.

É altamente recomendável mudar o estado ativado no callback para mudanças temporárias, porque ele mantém a ordem descrita anteriormente, o que é particularmente importante se você tiver callbacks registrados em vários proprietários de ciclo de vida aninhados diferentes.

Se você quiser remover o callback completamente, chame remove(). Isso geralmente não é necessário porque os callbacks são automaticamente removidos quando o LifecycleOwner associado é destruído.