Berinteraksi secara terprogram dengan komponen Navigation

Komponen Navigation menyediakan cara untuk membuat dan berinteraksi secara terprogram dengan elemen navigasi tertentu.

Membuat NavHostFragment

Anda dapat menggunakan NavHostFragment.create() untuk membuat NavHostFragment secara terprogram dengan aset grafik tertentu, seperti yang ditunjukkan dalam contoh di bawah ini:

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();

Perhatikan bahwa setPrimaryNavigationFragment(finalHost) memungkinkan NavHost menangkap penekanan tombol Kembali sistem. Anda juga dapat menerapkan perilaku ini dalam XML NavHost dengan menambahkan app:defaultNavHost="true". Jika Anda menerapkan perilaku tombol Kembali kustom dan tidak ingin NavHost menangkap penekanan tombol Kembali, Anda dapat meneruskan null ke setPrimaryNavigationFragment().

Dimulai dengan Navigation 2.2.0, Anda dapat memperoleh referensi ke NavBackStackEntry untuk setiap tujuan di stack navigasi dengan memanggil NavController.getBackStackEntry(), sambil meneruskan ID tujuan ke dalamnya. Jika data sebelumnya berisi lebih dari satu instance dari tujuan yang ditentukan, getBackStackEntry() menampilkan instance paling atas dari stack.

NavBackStackEntry yang ditampilkan menyediakan Lifecycle, ViewModelStore, dan SavedStateRegistry di tingkat tujuan. Objek ini berlaku selama tujuan masih aktif di entri data sebelumnya. Jika tujuan terkait dikeluarkan dari entri data sebelumnya, Lifecycle dihancurkan, status tidak akan disimpan lagi, dan objek ViewModel akan dihapus.

Properti ini memberi Anda Lifecycle dan penyimpanan untuk objek dan class ViewModel yang berfungsi dengan status tersimpan, apa pun jenis tujuan yang Anda gunakan. Hal ini berguna terutama saat bekerja dengan jenis tujuan yang tidak otomatis memiliki Lifecycle terkait, seperti tujuan khusus.

Misalnya, Anda dapat mengamati Lifecycle dari NavBackStackEntry sama seperti Anda mengamati Lifecycle fragmen atau aktivitas. Selain itu, NavBackStackEntry adalah LifecycleOwner, yang berarti Anda dapat menggunakannya saat mengamati LiveData atau dengan komponen berbasis siklus proses, seperti yang ditunjukkan dalam contoh berikut:

Kotlin

myViewModel.liveData.observe(backStackEntry, Observer { myData ->
    // react to live data update
})

Java

myViewModel.getLiveData().observe(backStackEntry, myData -> {
    // react to live data update
});

Status siklus proses otomatis diupdate setiap kali Anda memanggil navigate(). Status siklus proses untuk tujuan yang tidak berada di bagian atas data sebelumnya berpindah dari RESUMED ke STARTED jika tujuan masih terlihat di tujuan FloatingWindow, seperti tujuan dialog, atau ke STOPPED jika tidak.

Menampilkan hasil ke Tujuan sebelumnya

Dalam Navigation 2.3 dan yang lebih tinggi, NavBackStackEntry memberikan akses ke SavedStateHandle. SavedStateHandle adalah peta nilai kunci yang dapat digunakan untuk menyimpan dan mengambil data. Nilai-nilai ini akan dipertahankan setelah proses dihentikan, termasuk perubahan konfigurasi, dan akan tetap tersedia melalui objek yang sama. Dengan menggunakan SavedStateHandle yang diberikan, Anda dapat mengakses dan meneruskan data antar-tujuan. Hal ini sangat berguna sebagai mekanisme untuk mengembalikan data dari tujuan setelah data dikeluarkan dari stack.

Untuk meneruskan data kembali dari Tujuan B ke Tujuan A, tetapkan Tujuan A untuk memproses hasil di SavedStateHandle-nya. Untuk melakukannya, ambil NavBackStackEntry dengan menggunakan API getCurrentBackStackEntry() kemudian observe LiveData yang disediakan oleh 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.
        }
    });
}

Di Tujuan B, Anda harus set hasil di SavedStateHandle Tujuan A menggunakan API getPreviousBackStackEntry().

Kotlin

navController.previousBackStackEntry?.savedStateHandle?.set("key", result)

Java

navController.getPreviousBackStackEntry().getSavedStateHandle().set("key", result);

Jika hanya ingin menangani hasil satu kali, Anda harus memanggil remove() di SavedStateHandle untuk menghapus hasil. Jika hasil tidak dihapus, LiveData akan terus menampilkan hasil terakhir ke instance Observer baru.

Pertimbangan saat menggunakan tujuan dialog

Saat Anda melakukan navigate ke tujuan yang mengambil tampilan lengkap NavHost (seperti tujuan <fragment>), tujuan sebelumnya akan membuat siklus prosesnya terhenti, sehingga mencegah callback ke LiveData yang diberikan oleh SavedStateHandle.

Namun, saat menavigasi ke tujuan dialog, tujuan sebelumnya juga akan terlihat di layar dan juga STARTED meskipun bukan tujuan saat ini. Ini berarti bahwa panggilan ke getCurrentBackStackEntry() dari dalam metode siklus proses seperti onViewCreated() akan mengembalikan NavBackStackEntry tujuan dialog setelah perubahan konfigurasi atau proses dihentikan dan dibuat ulang (mengingat dialog dikembalikan di atas tujuan lainnya). Oleh karena itu, Anda harus menggunakan getBackStackEntry() dengan ID tujuan untuk memastikan bahwa Anda selalu menggunakan NavBackStackEntry yang benar.

Ini juga berarti bahwa Observer mana pun yang Anda tetapkan pada hasil LiveData akan terpicu meskipun tujuan dialog masih ada di layar. Jika hanya ingin memeriksa hasilnya saat tujuan dialog ditutup dan tujuan dasar menjadi tujuan saat ini, Anda dapat mengamati Lifecycle yang terkait dengan NavBackStackEntry dan mengambil hasilnya setelah menjadi 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)
            }
        }
    });
}

Entri data sebelumnya dari Navigation menyimpan NavBackStackEntry tidak hanya untuk setiap tujuan individu, tetapi juga untuk setiap grafik navigasi induk yang berisi tujuan individual. Hal ini memungkinkan Anda mengambil NavBackStackEntry yang dicakup dalam grafik navigasi. NavBackStackEntry cakupan grafik navigasi menyediakan cara untuk membuat ViewModel yang dicakupkan ke grafik navigasi, sehingga Anda dapat berbagi data terkait UI antar-tujuan grafik. Setiap objek ViewModel yang dibuat dengan cara ini akan aktif sampai NavHost yang terkait dan ViewModelStore-nya dibersihkan atau sampai grafik navigasi muncul dari entri data sebelumnya.

Contoh berikut menunjukkan cara mengambil ViewModel yang dicakup dalam grafik navigasi:

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);

Jika menggunakan Navigation 2.2.0 atau versi yang lebih lama, Anda harus menyediakan factory sendiri untuk menggunakan Status Tersimpan dengan ViewModels, seperti yang ditunjukkan dalam contoh berikut:

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());

Untuk mengetahui informasi selengkapnya tentang ViewModel, lihat Ringkasan ViewModel.

Mengubah grafik navigasi yang di-inflate

Anda dapat memodifikasi grafik navigasi yang di-inflate secara dinamis saat runtime.

Misalnya, jika Anda memiliki BottomNavigationView yang terikat dengan NavGraph, tujuan default NavGraph akan menentukan tab yang dipilih saat aplikasi dimulai. Namun, Anda mungkin perlu mengganti perilaku ini, seperti saat preferensi pengguna menentukan tab pilihan untuk dimuat saat aplikasi dimulai. Atau, aplikasi Anda mungkin perlu mengubah tab awal berdasarkan perilaku pengguna pada masa lalu. Anda dapat mendukung kasus ini dengan menentukan tujuan default NavGraph secara dinamis.

Pertimbangkan NavGraph ini:

<?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>

Saat grafik ini dimuat, atribut app:startDestination menentukan bahwa HomeFragment akan ditampilkan. Untuk mengganti tujuan awal secara dinamis, lakukan hal berikut:

  1. Pertama, inflate NavGraph secara manual.
  2. Ganti tujuan awal.
  3. Terakhir, lampirkan grafik secara manual ke 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);

Sekarang saat aplikasi Anda dimulai, ShopFragment akan ditampilkan, bukan HomeFragment.

Saat menggunakan deep link, NavController akan membuat data sebelumnya secara otomatis untuk tujuan deep link. Jika pengguna membuka deep link, lalu menavigasi mundur, mereka akan tetap mencapai tujuan awal pada akhirnya. Mengganti tujuan awal menggunakan teknik dalam contoh sebelumnya memastikan bahwa tujuan awal yang benar telah ditambahkan ke data sebelumnya yang telah dibuat.

Perhatikan bahwa teknik ini juga memungkinkan penggantian aspek lain dari NavGraph sebagaimana diperlukan. Semua modifikasi pada grafik harus dilakukan sebelum panggilan ke setGraph() untuk memastikan bahwa struktur yang benar digunakan saat menangani deep link, memulihkan status, dan berpindah ke tujuan awal grafik Anda.