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()
.
Referenciar um destino usando NavBackStackEntry
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) } } }); }
Compartilhar dados relacionados à IU entre destinos com ViewModel
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:
- Primeiro, infle o
NavGraph
manualmente. - Substitua o destino inicial.
- 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.