Navigation コンポーネントを使用すると、特定のナビゲーション要素をプログラムから作成したり操作したりできます。
NavHostFragment を作成する
NavHostFragment.create()
を使用して、特定のグラフリソースを持つ NavHostFragment
を作成できます。次の例をご覧ください。
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();
setPrimaryNavigationFragment(finalHost)
を使用すると、NavHost
でシステムの [戻る] ボタンをインターセプトできるようになります。同じ動作は、NavHost
XML に app:defaultNavHost="true"
を追加しても実現できます。カスタムの [戻る] ボタンの動作を実装していて、NavHost
に [戻る] ボタンをインターセプトさせたくない場合は、setPrimaryNavigationFragment()
に null
を渡します。
NavBackStackEntry を使用してデスティネーションを参照する
Navigation 2.2.0 以降では、デスティネーション ID を引数として NavController.getBackStackEntry()
を呼び出すと、ナビゲーション スタック上の任意のデスティネーションの NavBackStackEntry
への参照を取得できます。バックスタックに指定したデスティネーションの複数のインスタンスが含まれている場合、getBackStackEntry()
はスタックから最上位のインスタンスを返します。
返される NavBackStackEntry
からは、ディスティネーション レベルで Lifecycle
、ViewModelStore
、SavedStateRegistry
を取得できます。これらのオブジェクトは、バックスタックのデスティネーションが有効な間は、有効です。関連付けられているデスティネーションがバックスタックから取り出されると、Lifecycle
は破棄され、状態は保存されなくなり、ViewModel
オブジェクトはすべてクリアされます。
これらのプロパティは、使用するデスティネーションのタイプに関係なく、Lifecycle
と、保存された状態を扱う ViewModel
のオブジェクトとクラスの保存場所を提供します。これは、カスタムのデスティネーションなど、関連付けられた Lifecycle
を自動的に持たないデスティネーション タイプを扱う場合に、特に便利です。
たとえば、フラグメントやアクティビティの Lifecycle
を監視する場合と同じように、NavBackStackEntry
の Lifecycle
を監視できます。また、NavBackStackEntry
は LifecycleOwner
です。つまり、LiveData
を監視するときと、他のライフサイクルを認識するコンポーネントで使用できます。次の例をご覧ください。
Kotlin
myViewModel.liveData.observe(backStackEntry, Observer { myData -> // react to live data update })
Java
myViewModel.getLiveData().observe(backStackEntry, myData -> { // react to live data update });
navigate()
を呼び出すと、ライフサイクルの状態が自動的に更新されます。バックスタックの最上位にないデスティネーションが FloatingWindow
の下にあってもまだ見える場合、そのデスティネーションのライフサイクルは RESUMED
から STARTED
になり、見えない場合は STOPPED
になります。
前のデスティネーションに結果を返す
Navigation 2.3 以降の NavBackStackEntry
では SavedStateHandle
にアクセスできます。SavedStateHandle
はデータの保存と取得に使用できる Key-Value マップです。これらの値は、構成変更を含めてプロセス終了後も保持され、同じオブジェクトを通じて引き続き利用できます。指定された SavedStateHandle
を使用することで、デスティネーション間でデータのアクセスと受け渡しが可能です。これは、スタックから取り出されたデスティネーションからデータに取り戻すメカニズムとして特に便利です。
デスティネーション B からデスティネーション A にデータを返すには、まず、デスティネーション A を設定して、その SavedStateHandle
の結果をリッスンするようにします。そのためには、getCurrentBackStackEntry()
API を使用して NavBackStackEntry
を取得してから、SavedStateHandle
で提供される LiveData
を監視(observe
)します。
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. } }); }
デスティネーション B では、getPreviousBackStackEntry()
API を使用して、デスティネーション A の SavedStateHandle
に結果を設定(set
)する必要があります。
Kotlin
navController.previousBackStackEntry?.savedStateHandle?.set("key", result)
Java
navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);
結果を 1 回だけ処理したい場合は、SavedStateHandle
で remove()
を呼び出して、結果をクリアする必要があります。結果を削除しない場合、LiveData
は引き続き最後の結果を新しい Observer
インスタンスに返します。
ダイアログ デスティネーションを使用する際の考慮事項
NavHost
の全画面のビュー(<fragment>
デスティネーションなど)を持つデスティネーションに移動(navigate
)すると、前のデスティネーションはライフサイクルが停止し、SavedStateHandle
が提供する LiveData
へのコールバックはできなくなります。
しかし、ダイアログのデスティネーションに移動すると、前のデスティネーションは画面で見える状態にあるため、現在のデスティネーションでなくても STARTED
になります。つまり、onViewCreated()
などのライフサイクル メソッド内から getCurrentBackStackEntry()
を呼び出すと、構成変更やプロセスの終了と再作成の後でダイアログ デスティネーションの NavBackStackEntry
が返されます(ダイアログが別のデスティネーションの上に復元されるため)。そのため、常に正しい NavBackStackEntry
が使用されるように、デスティネーションの ID を指定して getBackStackEntry()
を使用してください。
また、結果の LiveData
に設定した Observer
は、ダイアログ デスティネーションが画面上にまだあってもトリガーされます。ダイアログ デスティネーションが閉じられて、下のデスティネーションが現在のデスティネーションになる場合にのみ結果を確認する場合は、NavBackStackEntry
に関連付けられた Lifecycle
を監視し、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) } } }); }
ViewModel を使用してデスティネーション間で UI 関連のデータを共有する
Navigation バックスタックには、個々のデスティネーションの NavBackStackEntry
だけでなく、個々のデスティネーションを含む各親ナビゲーション グラフの NavBackStackEntry も保存されます。これにより、ナビゲーション グラフにスコープ設定された NavBackStackEntry
を取得できます。ナビゲーション グラフにスコープ設定された NavBackStackEntry
では、ナビゲーション グラフにスコープ設定された ViewModel
を作成でき、これによってグラフのデスティネーション間で UI 関連のデータを共有できるようになります。この方法で作成された ViewModel
オブジェクトは、関連する NavHost
とその ViewModelStore
がクリアされるまで、またはナビゲーション グラフがバックスタックから取り出されるまで有効です。
次の例は、ナビゲーション グラフにスコープ設定された ViewModel
を取得する方法を示しています。
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);
Navigation 2.2.0 以前を使用している場合は、次の例に示すように、ViewModel の保存済み状態を使用する独自の Factory を提供する必要があります。
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());
ViewModel
の詳細については、ViewModel の概要をご覧ください。
インフレートされたナビゲーション グラフを変更する
インフレートされたナビゲーション グラフを実行時に動的に変更できます。
たとえば、BottomNavigationView
が NavGraph
にバインドされている場合、NavGraph
のデフォルトのデスティネーションによって、アプリの起動時に選択したタブを指定できます。ただし、ユーザー設定でアプリの起動時に読み込まれる優先タブが指定されている場合などは、この動作をオーバーライドする必要があります。また、過去のユーザーの行動に基づいて、開始タブを変更しなければならない場合もあります。このようなケースをサポートするには、NavGraph
のデフォルトのデスティネーションを動的に指定します。
この 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>
このグラフが読み込まれると、app:startDestination
属性によって HomeFragment
を表示するよう指定されます。開始デスティネーションを動的にオーバーライドする手順は次のとおりです。
- まず、
NavGraph
を手動でインフレートします。 - 開始デスティネーションをオーバーライドします。
- 最後に、グラフを
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);
アプリが起動したときに、HomeFragment
ではなく ShopFragment
が表示されるようになりました。
ディープリンクを使用している場合、NavController
によってディープリンク デスティネーションのバックスタックが自動的に作成されます。ユーザーがディープリンクに移動した後で戻ると、ある段階で開始デスティネーションに到達します。先ほどの例の方法で開始デスティネーションをオーバーライドすると、正しい開始デスティネーションが作成されたバックスタックに追加されます。
なお、この方法を使って必要に応じて NavGraph
の他の部分をオーバーライドすることもできます。グラフへの変更はすべて setGraph()
への呼び出しの前に行い、ディープリンクの処理、状態の復元、グラフの開始デスティネーションへの移動の際に、正しい構造が使用されるようにします。