Interagir programaticamente com o componente de navegação

O componente Navigation oferece maneiras de criar e interagir programaticamente com alguns elementos de navegação.

Criar um NavHostFragment

É possível usar NavHostFragment.create() para criar um NavHostFragment programaticamente com um recurso gráfico específico, como demonstrado no exemplo abaixo:

Kotlin

val finalHost = NavHostFragment.create(R.navigation.example_graph)
supportFragmentManager.beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
    .commit()

Java

NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph);
getSupportFragmentManager().beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // equivalent to app:defaultNavHost="true"
    .commit();

É importante lembrar que setPrimaryNavigationFragment(finalHost) permite que o NavHost intercepte os pressionamentos do botão "Voltar". Você também pode implementar esse comportamento no seu XML NavHost. Para isso, adicione app:defaultNavHost="true". Caso esteja implementando um comportamento personalizado do botão "Voltar" e não queira que o NavHost intercepte os pressionamentos do botão Voltar, você pode transmitir null para setPrimaryNavigationFragment().

A partir do Navigation 2.2.0, você pode acessar uma referência à NavBackStackEntry de qualquer destino na pilha de navegação chamando NavController.getBackStackEntry() e transmitindo-o como um ID de destino. Se a pilha de retorno contiver mais de uma instância do destino especificado, getBackStackEntry() retornará a instância superior da pilha.

A NavBackStackEntry retornada fornecem um Lifecycle, um ViewModelStore e um SavedStateRegistry no nível do destino. Esses objetos são válidos durante o ciclo de vida do destino na pilha de retorno. Quando o destino associado é retirado da pilha de retorno, o Lifecycle é destruído, o estado não é mais salvo e todos os objetos ViewModel são apagados.

Essas propriedades fornecem um Lifecycle e um armazenamento para objetos e classes ViewModel que funcionam com estado salvo, independentemente do tipo de destino usado. Isso é útil principalmente ao trabalhar com tipos de destino que não têm um Lifecycle associado automaticamente, como destinos personalizados.

Por exemplo, você pode observar o Lifecycle de um NavBackStackEntry da mesma forma que observaria o Lifecycle de um fragmento ou atividade. Além disso, NavBackStackEntry é um LifecycleOwner, o que significa que você pode usá-lo ao observar LiveData ou com outros componentes compatíveis com ciclos de vida, como mostrado no exemplo a seguir:

Kotlin

myViewModel.liveData.observe(backStackEntry, Observer { myData ->
    // react to live data update
})

Java

myViewModel.getLiveData().observe(backStackEntry, myData -> {
    // react to live data update
});

O estado do ciclo de vida é atualizado automaticamente sempre que você chama navigate(). Os estados de ciclo de vida para destinos que não estão no topo da pilha de retorno são movidos de RESUMED para STARTED se os destinos ainda estiverem visíveis em um destino FloatingWindow, como um destino da caixa de diálogo. ou para STOPPED, caso contrário.

Como retornar um resultado ao destino anterior

No Navigation 2.3 e em versões mais recentes, NavBackStackEntry concede acesso a um SavedStateHandle. Um SavedStateHandle é um mapa de chave-valor que pode ser usado para armazenar e extrair dados. Esses valores persistem após o encerramento do processo, incluindo alterações de configuração, e permanecem disponíveis pelo mesmo objeto. Ao usar o SavedStateHandle fornecido, você pode acessar e transmitir dados entre destinos. Isso é especialmente útil como mecanismo para recuperar dados de um destino depois que ele é retirado da pilha.

Para transmitir dados de volta ao destino A do destino B, primeiro configure o destino A para detectar um resultado no SavedStateHandle. Para fazer isso, recupere NavBackStackEntry usando a API getCurrentBackStackEntry() e, em seguida, observe o LiveData fornecido por SavedStateHandle.

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val navController = findNavController();
    // We use a String here, but any type that can be put in a Bundle is supported
    navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(
        viewLifecycleOwner) { result ->
        // Do something with the result.
    }
}

Java

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    NavController navController = NavHostFragment.findNavController(this);
    // We use a String here, but any type that can be put in a Bundle is supported
    MutableLiveData<String> liveData = navController.getCurrentBackStackEntry()
            .getSavedStateHandle()
            .getLiveData("key");
    liveData.observe(getViewLifecycleOwner(), new Observer<String>() {
        @Override
        public void onChanged(String s) {
            // Do something with the result.
        }
    });
}

No destino B, você precisa set o resultado em SavedStateHandle do destino A usando a API getPreviousBackStackEntry().

Kotlin

navController.previousBackStackEntry?.savedStateHandle?.set("key", result)

Java

navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);

Se você quiser lidar com um resultado apenas uma vez, chame remove() no SavedStateHandle para limpar o resultado. Se você não remover o resultado, o LiveData continuará retornando o último resultado para qualquer nova instância Observer.

Considerações ao usar destinos de caixas de diálogo

Quando você navigate para um destino que recebe a visualização completa do NavHost (como um destino <fragment>), o destino anterior tem seu ciclo de vida interrompido, evitando todos os callbacks para o LiveData fornecido por SavedStateHandle.

No entanto, ao navegar para um destino da caixa de diálogo, o destino anterior também fica visível na tela e, portanto, também é STARTED, embora não seja o destino atual. Isso significa que chamadas para getCurrentBackStackEntry() de métodos de ciclo de vida, como onViewCreated(), vão retornar NavBackStackEntry do destino da caixa de diálogo após uma mudança de configuração ou de uma interrupção e recriação de um processo. Isso acontece desde que a caixa de diálogo seja restaurada acima do outro destino. Portanto, use getBackStackEntry() com o ID do destino para garantir que o NavBackStackEntry correto sempre seja usado.

Isso também significa que qualquer Observer definido no resultado LiveData será acionado mesmo enquanto os destinos da caixa de diálogo ainda estiverem na tela. Se você só quiser verificar o resultado quando o destino da caixa de diálogo for fechado e o destino anterior se tornar o destino atual, você poderá observar o Lifecycle associado ao NavBackStackEntry e recuperar o resultado apenas quando ele se tornar RESUMED.

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val navController = findNavController();
    // After a configuration change or process death, the currentBackStackEntry
    // points to the dialog destination, so you must use getBackStackEntry()
    // with the specific ID of your destination to ensure we always
    // get the right NavBackStackEntry
    val navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment)

    // Create our observer and add it to the NavBackStackEntry's lifecycle
    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_RESUME
            && navBackStackEntry.savedStateHandle.contains("key")) {
            val result = navBackStackEntry.savedStateHandle.get<String>("key");
            // Do something with the result
        }
    }
    navBackStackEntry.lifecycle.addObserver(observer)

    // As addObserver() does not automatically remove the observer, we
    // call removeObserver() manually when the view lifecycle is destroyed
    viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_DESTROY) {
            navBackStackEntry.lifecycle.removeObserver(observer)
        }
    })
}

Java

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    NavController navController = NavHostFragment.findNavController(this);
    // After a configuration change or process death, the currentBackStackEntry
    // points to the dialog destination, so you must use getBackStackEntry()
    // with the specific ID of your destination to ensure we always
    // get the right NavBackStackEntry
    final NavBackStackEntry navBackStackEntry = navController.getBackStackEntry(R.id.your_fragment);

    // Create our observer and add it to the NavBackStackEntry's lifecycle
    final LifecycleEventObserver observer = new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
            if (event.equals(Lifecycle.Event.ON_RESUME)
                && navBackStackEntry.getSavedStateHandle().contains("key")) {
                String result = navBackStackEntry.getSavedStateHandle().get("key");
                // Do something with the result
            }
        }
    };
    navBackStackEntry.getLifecycle().addObserver(observer);

    // As addObserver() does not automatically remove the observer, we
    // call removeObserver() manually when the view lifecycle is destroyed
    getViewLifecycleOwner().getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
            if (event.equals(Lifecycle.Event.ON_DESTROY)) {
                navBackStackEntry.getLifecycle().removeObserver(observer)
            }
        }
    });
}

A pilha de retorno do Navigation armazena uma NavBackStackEntry não apenas para cada destino individual, mas também para cada gráfico de navegação pai que contém o destino individual. Isso permite que você recupere um NavBackStackEntry com o escopo do gráfico de navegação. Uma NavBackStackEntry com escopo do gráfico de navegação fornece uma maneira de criar um ViewModel com o escopo do gráfico de navegação, permitindo o compartilhamento de dados relacionados à IU entre os destinos do gráfico. Quaisquer objetos ViewModel criados dessa forma são válidos até que o NavHost associado e o ViewModelStore dele sejam eliminados ou que o gráfico de navegação seja retirado da pilha de retorno.

O exemplo a seguir mostra como recuperar um ViewModel com o escopo do gráfico de navegação:

Kotlin

val viewModel: MyViewModel
        by navGraphViewModels(R.id.my_graph)

Java

NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);
MyViewModel viewModel = new ViewModelProvider(backStackEntry).get(MyViewModel.class);

Se você estiver usando o Navigation 2.2.0 ou uma versão anterior, será necessário fornecer sua própria fábrica para usar o estado salvo com ViewModels, como mostrado no exemplo a seguir:

Kotlin

val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph) {
    SavedStateViewModelFactory(requireActivity().application, requireParentFragment())
}

Java

NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);

ViewModelProvider viewModelProvider = new ViewModelProvider(
        backStackEntry.getViewModelStore(),
        new SavedStateViewModelFactory(
                requireActivity().getApplication(), requireParentFragment()));

MyViewModel myViewModel = provider.get(myViewModel.getClass());

Para mais informações sobre ViewModel, consulte a Visão geral do ViewModel.

Como modificar gráficos de navegação inflados

Você pode modificar um gráfico de navegação inflado dinamicamente durante a execução.

Por exemplo, se você tiver uma BottomNavigationView vinculada a um NavGraph, o destino padrão do NavGraph determina a aba selecionada na inicialização do app. No entanto, pode ser necessário substituir esse comportamento, como quando uma preferência do usuário especificar uma guia preferencial para ser carregada na inicialização do app. Como alternativa, seu app pode precisar mudar a guia inicial com base no comportamento anterior do usuário. É possível oferecer suporte a esses casos, especificando o destino padrão do NavGraph de forma dinâmica.

Considere este NavGraph:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/home">
    <fragment
        android:id="@+id/home"
        android:name="com.example.android.navigation.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" />
    <fragment
        android:id="@+id/location"
        android:name="com.example.android.navigation.LocationFragment"
        android:label="fragment_location"
        tools:layout="@layout/fragment_location" />
    <fragment
        android:id="@+id/shop"
        android:name="com.example.android.navigation.ShopFragment"
        android:label="fragment_shop"
        tools:layout="@layout/fragment_shop" />
    <fragment
        android:id="@+id/settings"
        android:name="com.example.android.navigation.SettingsFragment"
        android:label="fragment_settings"
        tools:layout="@layout/fragment_settings" />
</navigation>

Quando esse gráfico é carregado, o atributo app:startDestination especifica que o HomeFragment será mostrado. Para substituir o destino inicial de forma dinâmica, faça o seguinte:

  1. Primeiro, infle o NavGraph manualmente.
  2. Substitua o destino inicial.
  3. Por fim, anexe o gráfico manualmente ao NavController.

Kotlin

val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

val navController = navHostFragment.navController
val navGraph = navController.navInflater.inflate(R.navigation.bottom_nav_graph)
navGraph.startDestination = R.id.shop
navController.graph = navGraph
binding.bottomNavView.setupWithNavController(navController)

Java

NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
        .findFragmentById(R.id.nav_host_fragment);

NavController navController = navHostFragment.getNavController();
NavGraph navGraph = navController.getNavInflater().inflate(R.navigation.bottom_nav_graph);
navGraph.setStartDestination(R.id.shop);
navController.setGraph(navGraph);
NavigationUI.setupWithNavController(binding.bottomNavView, navController);

Agora, quando o app for iniciado, o ShopFragment será exibido em vez do HomeFragment.

Ao usar links diretos, o NavController constrói uma backstack automaticamente para o destino do link direto. Se o usuário navegar para o link direto e, em seguida, voltar, ele chegará ao destino inicial em algum momento. A substituição do destino inicial usando a técnica do exemplo anterior garante que o destino correto seja adicionado à backstack construída.

Essa técnica também permite a substituição de outros aspectos do NavGraph, conforme necessário. Todas as modificações no gráfico precisam ser feitas antes da chamada de setGraph() para garantir que a estrutura correta seja usada ao processar links diretos, restaurar o estado e mover para o destino inicial do gráfico.