プログラムで Navigation コンポーネントを操作する

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 を渡します。

Navigation 2.2.0 以降では、デスティネーション ID を引数として NavController.getBackStackEntry() を呼び出すと、ナビゲーション スタック上の任意のデスティネーションの NavBackStackEntry への参照を取得できます。バックスタックに指定したデスティネーションの複数のインスタンスが含まれている場合、getBackStackEntry() はスタックから最上位のインスタンスを返します。

返される NavBackStackEntry からは、ディスティネーション レベルで LifecycleViewModelStoreSavedStateRegistry を取得できます。これらのオブジェクトは、バックスタックのデスティネーションが有効な間は、有効です。関連付けられているデスティネーションがバックスタックから取り出されると、Lifecycle は破棄され、状態は保存されなくなり、ViewModel オブジェクトはすべてクリアされます。

これらのプロパティは、使用するデスティネーションのタイプに関係なく、Lifecycle と、保存された状態を扱う ViewModel のオブジェクトとクラスの保存場所を提供します。これは、カスタムのデスティネーションなど、関連付けられた Lifecycle を自動的に持たないデスティネーション タイプを扱う場合に、特に便利です。

たとえば、フラグメントやアクティビティの Lifecycle を監視する場合と同じように、NavBackStackEntryLifecycle を監視できます。また、NavBackStackEntryLifecycleOwner です。つまり、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 回だけ処理したい場合は、SavedStateHandleremove() を呼び出して、結果をクリアする必要があります。結果を削除しない場合、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)
            }
        }
    });
}

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 の概要をご覧ください。

インフレートされたナビゲーション グラフを変更する

インフレートされたナビゲーション グラフを実行時に動的に変更できます。

たとえば、BottomNavigationViewNavGraph にバインドされている場合、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 を表示するよう指定されます。開始デスティネーションを動的にオーバーライドする手順は次のとおりです。

  1. まず、NavGraph を手動でインフレートします。
  2. 開始デスティネーションをオーバーライドします。
  3. 最後に、グラフを 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() への呼び出しの前に行い、ディープリンクの処理、状態の復元、グラフの開始デスティネーションへの移動の際に、正しい構造が使用されるようにします。