El componente Navigation ofrece maneras de crear determinados elementos de navegación y también interactuar con ellos de manera programática.
Cómo crear un NavHostFragment
Puedes usar NavHostFragment.create()
para crear de manera programática un NavHostFragment
con un recurso de gráfico específico, como se muestra en el siguiente ejemplo:
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();
Ten presente que setPrimaryNavigationFragment(finalHost)
permite que el NavHost
intercepte las activaciones del botón Atrás del sistema. También puedes implementar este comportamiento en el XML del NavHost
agregando app:defaultNavHost="true"
. Si implementas un comportamiento personalizado del botón Atrás y no quieres que el NavHost
intercepte las activaciones del botón Atrás, puedes pasar null
a setPrimaryNavigationFragment()
.
Cómo hacer referencia a un destino con NavBackStackEntry
A partir de Navigation 2.2.0, puedes obtener una referencia a NavBackStackEntry
para cualquier destino de la pila de navegación llamando a NavController.getBackStackEntry()
y pasándole un ID de destino. Si la pila de actividades contiene más de una instancia del destino especificado, getBackStackEntry()
muestra la instancia superior de la pila.
La NavBackStackEntry
que se muestra proporciona un Lifecycle
, un ViewModelStore
y un SavedStateRegistry
en el nivel de destino. Estos objetos son válidos durante toda la vida útil del destino en la pila de actividades. Cuando el destino asociado se quita de la pila de actividades, se destruye el Lifecycle
, el estado ya no se guarda y se borran los objetos ViewModel
.
Estas propiedades te ofrecen un objeto Lifecycle
y un almacén para objetos ViewModel
y clases que funcionan con el estado guardado, sin importar el tipo de destino que uses. Esto es en sobre todo útil cuando se trabaja con tipos de destino que no tienen un Lifecycle
asociado automáticamente, como los destinos personalizados.
Por ejemplo, puedes observar el Lifecycle
de una NavBackStackEntry
como lo harías con el Lifecycle
de un fragmento o una actividad. Además, NavBackStackEntry
es un LifecycleOwner
, lo que significa que puedes usarla cuando se observa LiveData
o con otros componentes optimizados para ciclos de vida, como se muestra en el siguiente ejemplo:
Kotlin
myViewModel.liveData.observe(backStackEntry, Observer { myData -> // react to live data update })
Java
myViewModel.getLiveData().observe(backStackEntry, myData -> { // react to live data update });
El estado del ciclo de vida se actualiza automáticamente cada vez que llamas a navigate()
.
Los estados del ciclo de vida de los destinos que no están en la parte superior de la pila de actividades pasan de RESUMED
a STARTED
si los destinos aún están visibles en un destino FloatingWindow
, como un destino de diálogo o, de lo contrario, STOPPED
.
Cómo mostrar un resultado al destino anterior
En Navigation 2.3 y versiones posteriores, NavBackStackEntry
otorga acceso a un SavedStateHandle
.
Un SavedStateHandle
es un mapa de pares clave-valor que se puede usar para almacenar y recuperar datos. Estos valores persisten durante la finalización del proceso, incluidos los cambios de configuración, y permanecen disponibles a través del mismo objeto. Si usas un objeto SavedStateHandle
determinado, puedes acceder a los datos y pasarlos entre destinos.
Esto es particularmente útil como un mecanismo para recuperar datos de un destino después de que se quita de la pila.
A fin de pasar datos de vuelta al destino A desde el destino B, primero configura el destino A para que detecte un resultado en su SavedStateHandle
.
Para hacerlo, recupera la NavBackStackEntry
mediante la API getCurrentBackStackEntry()
y, luego, implementa observe
en el elemento LiveData
proporcionado 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. } }); }
En el destino B, debes set
el resultado en el SavedStateHandle
del destino A mediante la API getPreviousBackStackEntry()
.
Kotlin
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
Java
navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);
Si solo deseas controlar un resultado una sola vez, debes llamar a remove()
en el SavedStateHandle
para borrarlo. Si no quitas el resultado, LiveData
seguirá mostrando el último resultado a cualquier instancia de Observer
nueva.
Consideraciones para el uso de destinos de diálogo
Cuando implementas navigate
en un destino que toma la vista completa de NavHost
(como un destino <fragment>
), el destino anterior tiene su ciclo de vida detenido, lo que evita cualquier devolución de llamada a LiveData
, que proporciona SavedStateHandle
.
Sin embargo, cuando navegas a un destino de diálogo, el destino anterior también es visible en la pantalla y, por lo tanto, STARTED
, a pesar de no ser el destino actual. Esto significa que las llamadas a getCurrentBackStackEntry()
desde métodos de ciclo de vida, como onViewCreated()
, mostrarán la NavBackStackEntry
del destino del diálogo después de un cambio en la configuración o una finalización del proceso y la recreación (desde el diálogo se restablece arriba del otro destino). Por lo tanto, debes usar getBackStackEntry()
con el ID de tu destino para asegurarte de usar siempre la NavBackStackEntry
correcta.
Esto también significa que cualquier Observer
que configures en el resultado LiveData
se activará incluso mientras los destinos del diálogo aún estén en pantalla. Si solo deseas verificar el resultado cuando se cierra el destino del diálogo y el destino subyacente se convierte en el destino actual, puedes observar el Lifecycle
asociado con la NavBackStackEntry
y recuperar el resultado solo cuando se convierte en 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) } } }); }
Cómo compartir datos relacionados con la IU entre destinos con ViewModel
La pila de actividades de Navigation almacena una NavBackStackEntry
no solo correspondiente a cada destino individual, sino también para cada gráfico de navegación superior que contenga el destino individual. Esto te permite recuperar una NavBackStackEntry
específica de un gráfico de navegación. Una NavBackStackEntry
con alcance de navegación proporciona una forma de crear un ViewModel
definido para un gráfico de navegación, lo que te permite compartir datos relacionados con la IU entre los destinos del gráfico. Todo objeto ViewModel
creado de esta manera está activado hasta que el NavHost
asociado y su ViewModelStore
se borren o hasta que el gráfico de navegación salga de la pila de actividades.
En el siguiente ejemplo se muestra cómo recuperar un ViewModel
definido según un gráfico de navegación:
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);
Si usas Navigation 2.2.0 o versiones anteriores, debes proporcionar tu propia fábrica para usar el Estado guardado con ViewModels, como se muestra en el siguiente ejemplo:
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 obtener más información sobre ViewModel
, consulta la sección Descripción general de ViewModel.
Cómo modificar gráficos de navegación aumentados
Puedes modificar un gráfico de navegación aumentado de forma dinámica durante el tiempo de ejecución.
Por ejemplo, si tienes una BottomNavigationView
que está vinculada a un NavGraph
, el destino predeterminado de NavGraph
determina la pestaña seleccionada al iniciar la app. Sin embargo, es posible que debas anular este comportamiento, por ejemplo, cuando una preferencia del usuario especifica una pestaña preferida para cargar durante el inicio de la app. Como alternativa, quizás la app deba cambiar la pestaña de inicio según el comportamiento anterior del usuario. Puedes admitir estos casos si especificas de forma dinámica el destino predeterminado del NavGraph
.
Considera 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>
Cuando se carga este gráfico, el atributo app:startDestination
especifica que se debe mostrar HomeFragment
. Para anular el destino de inicio de forma dinámica, haz lo siguiente:
- Primero, aumenta el
NavGraph
de forma manual. - Anula el destino de inicio.
- Por último, adjunta el gráfico al
NavController
de forma manual.
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);
Ahora, cuando se inicia la app, se muestra ShopFragment
en lugar de HomeFragment
.
Cuando usas vínculos directos, NavController
construye automáticamente una pila de actividades para el destino del vínculo directo. Si el usuario navega al vínculo directo y luego retrocede, llegará al destino de inicio en algún momento. La anulación del destino de inicio con la técnica del ejemplo anterior garantiza que se agregue el destino de inicio correcto a la pila de actividades construida.
Ten en cuenta que esta técnica también permite anular otros aspectos del NavGraph
según sea necesario. Todas las modificaciones del gráfico deben realizarse antes de la llamada a setGraph()
para garantizar que se use la estructura correcta cuando se manejen vínculos directos, se restablezca el estado y se realice el traslado al destino de inicio del gráfico.