Le composant Navigation permet de créer et d'interagir de manière programmatique avec certains éléments de navigation.
Créer un NavHostFragment
NavHostFragment.create()
vous permet de créer programmatiquement un objet NavHostFragment
avec une ressource de graphique spécifique, comme illustré dans l'exemple ci-dessous :
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();
Notez que setPrimaryNavigationFragment(finalHost)
permet à votre NavHost
d'intercepter les appuis sur le bouton Retour. Vous pouvez également implémenter ce comportement dans votre fichier XML NavHost
en ajoutant app:defaultNavHost="true"
. Si vous implémentez un comportement personnalisé du bouton Retour et ne souhaitez pas que votre NavHost
intercepte les appuis sur le bouton Retour, vous pouvez transmettre null
à setPrimaryNavigationFragment()
Faire référence à une destination avec NavBackStackEntry
À partir de Navigation 2.2.0, vous pouvez obtenir une référence à NavBackStackEntry
pour n'importe quelle destination de la pile de navigation en appelant NavController.getBackStackEntry()
, en lui transmettant un ID de destination. Si la pile "Retour" contient plusieurs instances de la destination spécifiée, getBackStackEntry()
renvoie l'instance la plus élevée de la pile.
La valeur NavBackStackEntry
renvoyée fournit un Lifecycle
, unViewModelStore
et un SavedStateRegistry
au niveau de la destination. Ces objets sont valides pour la durée de vie de la destination dans la pile "Retour". Lorsque la destination associée est retirée de la pile "Retour", l'élément Lifecycle
est détruit, l'état n'est plus enregistré et les objets ViewModel
sont effacés.
Ces propriétés vous donnent un Lifecycle
et un magasin pour les objets ViewModel
et les classes qui fonctionnent avec un état enregistré, quel que soit le type de destination que vous utilisez. Cela est particulièrement utile lorsque vous utilisez des types de destination auxquels aucun Lifecycle
n'est automatiquement associé, tels que des destinations personnalisées.
Par exemple, vous pouvez observer le Lifecycle
d'une NavBackStackEntry
comme vous le feriez pour le Lifecycle
d'un fragment ou d'une activité. En outre, NavBackStackEntry
est une LifecycleOwner
, ce qui signifie que vous pouvez l'utiliser pour observer LiveData
ou avec d'autres composants compatibles avec le cycle de vie, comme illustré dans l'exemple ci-après :
Kotlin
myViewModel.liveData.observe(backStackEntry, Observer { myData -> // react to live data update })
Java
myViewModel.getLiveData().observe(backStackEntry, myData -> { // react to live data update });
L'état du cycle de vie est automatiquement mis à jour chaque fois que vous appelez navigate()
.
Les états du cycle de vie des destinations qui ne se trouvent pas en haut de la pile "Retour" passent de RESUMED
à STARTED
si les destinations sont toujours visibles sous une destination FloatingWindow
, par exemple une boîte de dialogue, ou bien à STOPPED
dans le cas contraire.
Renvoyer un résultat à la destination précédente
Dans Navigation 2.3 et versions ultérieures, NavBackStackEntry
donne accès à un SavedStateHandle
.
Un SavedStateHandle
est un mappage clé-valeur qui peut être utilisé pour stocker et récupérer des données. Ces valeurs sont conservées jusqu'à l'arrêt du processus, y compris les modifications de configuration, et restent disponibles via le même objet. En utilisant le SavedStateHandle
donné, vous pouvez accéder aux données et les transmettre entre les destinations.
Cela est particulièrement utile comme mécanisme permettant de récupérer des données depuis une destination après qu'elles ont été extraites de la pile.
Pour transmettre des données à la destination A à partir de la destination B, commencez par configurer la destination A pour qu'elle écoute un résultat sur son SavedStateHandle
.
Pour ce faire, récupérez NavBackStackEntry
à l'aide de l'API getCurrentBackStackEntry()
, puis utilisez observe
pour observer les données LiveData
fournies par 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. } }); }
Dans la destination B, vous devez utiliser set
pour définir le résultat du SavedStateHandle
de la destination A à l'aide de l'API getPreviousBackStackEntry()
.
Kotlin
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
Java
navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);
Si vous souhaitez gérer un résultat une seule fois, vous devez appeler remove()
sur l'élément SavedStateHandle
pour l'effacer. Si vous ne supprimez pas le résultat, LiveData
continuera de renvoyer le dernier résultat à toutes les nouvelles instances Observer
.
Considérations concernant l'utilisation de destinations de boîtes de dialogue
Lorsque vous utilisez navigate
pour atteindre une destination qui affiche l'intégralité du NavHost
(par exemple, une destination <fragment>
), son cycle de vie est arrêté, ce qui empêche le rappel des LiveData
fournies par SavedStateHandle
.
Toutefois, lorsque vous accédez à une destination de boîte de dialogue, la destination précédente est également visible à l'écran et est donc également STARTED
, même s'il ne s'agit pas de la destination actuelle. Cela signifie que les appels à getCurrentBackStackEntry()
à partir de méthodes de cycle de vie telles que onViewCreated()
renverront la NavBackStackEntry
de la destination de la boîte de dialogue après une modification de configuration ou la fin et la recréation d'un processus (étant donné que la boîte de dialogue est restaurée au-dessus de l'autre destination). Par conséquent, vous devez utiliser getBackStackEntry()
avec l'ID de votre destination pour vous assurer de toujours utiliser la bonne NavBackStackEntry
.
Cela signifie également que toutes les objets Observer
que vous définissez sur le résultat LiveData
se déclencheront même si les destinations de la boîte de dialogue sont toujours à l'écran. Si vous ne souhaitez vérifier le résultat que lorsque la destination de la boîte de dialogue est fermée et que la destination sous-jacente devient la destination actuelle, vous pouvez observer le Lifecycle
associé à la NavBackStackEntry
et récupérer le résultat uniquement lorsque l'état devient 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) } } }); }
Partager des données d'interface utilisateur entre les destinations avec ViewModel
La pile "Retour" de Navigation stocke un élément NavBackStackEntry
non seulement pour chaque destination, mais aussi pour chaque graphique de navigation parent contenant la destination. Cela vous permet de récupérer une NavBackStackEntry
limitée à un graphique de navigation. Une NavBackStackEntry
, dont la portée est définie au niveau du graphique de navigation, permet de créer un ViewModel
limité à un graphique de navigation. Vous pouvez ainsi partager des données liées à l'interface utilisateur entre les destinations du graphique. Tous les objets ViewModel
créés de cette manière sont actifs jusqu'à ce que l'élément NavHost
et son élément ViewModelStore
soient effacés ou jusqu'à ce que le graphique de navigation soit retiré de la pile "Retour".
L'exemple suivant montre comment récupérer un élément ViewModel
limité à un graphique de navigation :
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 vous utilisez Navigation 2.2.0 ou une version antérieure, vous devez fournir votre propre fabrique pour utiliser l'état enregistré avec des ViewModels, comme indiqué dans l'exemple suivant :
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());
Pour en savoir plus sur ViewModel
, consultez la page Présentation de ViewModel.
Modifier les graphiques de navigation gonflés
Vous pouvez modifier un graphique de navigation gonflé de manière dynamique au moment de l'exécution.
Par exemple, si vous disposez d'un objet BottomNavigationView
lié à un objet NavGraph
, la destination par défaut de cet objet NavGraph
dicte le nom au démarrage de l'application. Toutefois, vous devrez peut-être ignorer ce comportement, par exemple lorsqu'une préférence utilisateur spécifie un onglet préféré à charger au démarrage de l'application. Votre application peut également avoir besoin de modifier l'onglet de démarrage en fonction du comportement antérieur des utilisateurs. Vous pouvez accepter ces cas en spécifiant dynamiquement la destination par défaut de NavGraph
.
Examinez le NavGraph
suivant :
<?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>
Lorsque ce graphique est chargé, l'attribut app:startDestination
spécifie que HomeFragment
doit être affiché. Pour remplacer la destination de départ de manière dynamique, procédez comme suit :
- Commencez par gonfler manuellement le
NavGraph
. - Ignorez la destination de départ.
- Enfin, associez manuellement le graphique au
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);
Désormais, au démarrage de l'application, ShopFragment
s'affiche à la place de HomeFragment
.
Lorsque vous utilisez des liens profonds, le NavController
crée automatiquement une pile "Retour" pour la destination du lien profond. Si l'utilisateur accède au lien profond, puis revient en arrière, il parvient à la destination de départ à un moment donné. Le remplacement de la destination de départ à l'aide de la technique de l'exemple précédent garantit que la bonne destination est ajoutée à la pile "Retour" construite.
Notez que cette technique permet également de remplacer d'autres aspects du NavGraph
, si nécessaire. Toutes les modifications du graphique doivent être effectuées avant l'appel de la méthode setGraph()
pour garantir que la structure correcte est utilisée lors du traitement des liens profonds, de la restauration de l'état et du déplacement de la propriété la destination de départ de votre graphique.