Tương tác theo cách lập trình với thành phần Điều hướng

Thành phần Điều hướng cung cấp nhiều cách để tạo và tương tác theo phương pháp có lập trình với một số phần tử điều hướng nhất định.

Tạo NavHostFragment

Bạn có thể sử dụng NavHostFragment.create() để theo cách lập trình tạo NavHostFragment với một tài nguyên biểu đồ cụ thể, như trong ví dụ dưới đây:

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

Lưu ý rằng setPrimaryNavigationFragment(finalHost) cho phép NavHost chặn hệ thống nhấn nút Quay lại. Bạn cũng có thể triển khai hành vi này trong XML của NavHost bằng cách thêm app:defaultNavHost="true". Nếu đang triển khai hành vi tuỳ chỉnh của nút Quay lại và không muốn NavHost chặn hành vi nhấn nút Quay lại, bạn có thể chuyển null đến setPrimaryNavigationFragment().

Từ Navigation 2.2.0, bạn có thể nhận tham chiếu đến NavBackStackEntry cho bất kỳ đích đến nào trong ngăn xếp điều hướng khi gọi NavController.getBackStackEntry(), đồng thời chuyển mã nhận dạng đích đến vào đó. Nếu ngăn xếp lui chứa nhiều phiên bản của đích đến đã định, thì getBackStackEntry() sẽ trả về phiên bản trên cùng từ ngăn xếp.

NavBackStackEntry được trả về cung cấp một Lifecycle, mộtViewModelStoreSavedStateRegistry tại cấp độ đích đến. Các đối tượng này hợp lệ trong toàn thời gian tồn tại của đích đến trên ngăn xếp lui. Khi vị trí có liên quan được kéo ra khỏi ngăn xếp lui, Lifecycle sẽ bị huỷ, trạng thái không còn được lưu và mọi đối tượng ViewModel đều bị xoá.

Những thuộc tính này mang đến cho bạn một Lifecycle và một cửa hàng cho các đối tượng cũng như lớp ViewModel hoạt động với trạng thái đã lưu bất kể bạn sử dụng loại đích đến nào. Điều này đặc biệt hữu ích khi làm việc với các loại đích đến mà không tự động có Lifecycle liên kết, chẳng hạn như các đích đến tuỳ chỉnh.

Ví dụ: bạn có thể quan sát Lifecycle của NavBackStackEntry giống như khi bạn quan sát Lifecycle của một mảnh hoặc hoạt động. Ngoài ra, NavBackStackEntry là một LifecycleOwner, có nghĩa là bạn có thể sử dụng mã này khi quan sát LiveData hoặc với các thành phần nhận biết được vòng đời khác, như trong ví dụ sau:

Kotlin

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

Java

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

Trạng thái vòng đời sẽ tự động cập nhật bất cứ khi nào bạn gọi navigate(). Trạng thái vòng đời cho đích đến không nằm ở đầu ngăn xếp lui sẽ di chuyển từ RESUMED sang STARTED nếu đích đến vẫn hiển thị trong đích đến FloatingWindow, chẳng hạn như đích đến hộp thoại hoặc đến STOPPED.

Trả lại kết quả về đích đến trước đó

Trong Navigation 2.3 trở lên, NavBackStackEntry cấp quyền truy cập cho SavedStateHandle. SavedStateHandle là một bản đồ khoá-giá trị có thể dùng để lưu trữ và truy xuất dữ liệu. Các giá trị này vẫn tồn tại cho đến khi bị buộc tắt, bao gồm cả thay đổi cấu hình và vẫn sẵn sàng hoạt động qua cùng đối tượng đó. Bằng cách sử dụng SavedStateHandle đã cho, bạn có thể truy cập và chuyển dữ liệu giữa các đích đến. Điều này đặc biệt hữu ích khi là cơ chế để lấy lại dữ liệu từ một đích đến sau khi được kéo khỏi ngăn xếp.

Để chuyển dữ liệu quay lại Đích đến A từ Đích đến B, trước tiên, thiết lập Đích đến A để nghe kết quả trên SavedStateHandle của đích đến đó. Để thực hiện việc này, truy xuất NavBackStackEntry bằng API getCurrentBackStackEntry() rồi observe LiveData do SavedStateHandle cung cấp.

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

Trong Đích đến B, bạn phải set kết quả trên SavedStateHandle của Đích đến A bằng cách sử dụng API getPreviousBackStackEntry().

Kotlin

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

Java

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

Nếu chỉ muốn xử lý kết quả một lần, bạn phải gọi remove() trên SavedStateHandle để xoá kết quả. Nếu bạn không xoá kết quả, LiveData sẽ tiếp tục trả về kết quả gần đây nhất cho mọi phiên bản Observer mới.

Điều cần cân nhắc khi sử dụng hộp thoại đích đến

Khi bạn navigate tới một đích đến mà có chế độ xem toàn bộ của NavHost (chẳng hạn như đích đến <fragment>), thì đích đến trước đã ngừng vòng đời của mình, do vậy ngăn mọi lệnh gọi lại đến LiveData do SavedStateHandle cung cấp.

Tuy nhiên, khi di chuyển đến một đích đến hộp thoại, đích đến trước cũng sẽ hiển thị trên màn hình và do đó cũng STARTED mặc dù không phải là đích đến hiện tại. Điều này có nghĩa là các lệnh gọi đến getCurrentBackStackEntry() từ bên trong phương thức vòng đời (chẳng hạn như onViewCreated()) sẽ trả về NavBackStackEntry của đích đến hộp thoại sau khi thay đổi cấu hình hoặc bị buộc tắt và tái tạo (do hộp thoại được khôi phục bên trên đích đến khác). Vì vậy, bạn nên sử dụng getBackStackEntry() cùng mã nhận dạng đích đến để đảm bảo luôn sử dụng đúng NavBackStackEntry.

Điều này cũng có nghĩa là bất kỳ Observer nào bạn đặt cho kết quả LiveData sẽ được kích hoạt ngay cả khi các đích đến hộp thoại vẫn còn trên màn hình. Nếu chỉ muốn kiểm tra kết quả khi đích đến hộp thoại đang đóng và đích đến bên dưới trở thành đích đến hiện tại, thì bạn có thể quan sát Lifecycle được liên kết với NavBackStackEntry và chỉ truy xuất kết quả khi nó trở thành 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)
            }
        }
    });
}

Ngăn xếp lui Điều hướng không chỉ lưu trữ một NavBackStackEntry cho từng đích đến riêng lẻ mà còn cho từng biểu đồ điều hướng mẹ có chứa đích đến riêng lẻ. Điều này cho phép bạn truy xuất NavBackStackEntry trong phạm vi biểu đồ điều hướng. NavBackStackEntry trong phạm vi biểu đồ điều hướng cung cấp một cách để tạo ViewModel thuộc phạm vi của biểu đồ điều hướng, cho phép bạn chia sẻ dữ liệu liên quan đến giao diện người dùng giữa các đích đến của biểu đồ. Mọi đối tượng ViewModel được tạo theo cách này sẽ tồn tại cho đến khi NavHost được liên kết và ViewModelStore của nó bị xoá hoặc cho đến khi biểu đồ điều hướng được kéo ra khỏi ngăn xếp lui.

Ví dụ sau cho thấy cách truy xuất ViewModel được định phạm vi trong một biểu đồ điều hướng:

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

Nếu đang sử dụng Điều hướng 2.2.0 trở về trước, bạn cần cung cấp nhà máy riêng của mình để sử dụng Trạng thái đã lưu với ViewModel, như trong ví dụ sau:

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

Để biết thêm thông tin về ViewModel, vui lòng xem nội dung Tổng quan về ViewModel.

Sửa đổi biểu đồ điều hướng tăng cường

Bạn có thể sửa đổi biểu đồ điều hướng mở rộng theo phương thức động trong thời gian chạy.

Ví dụ: nếu bạn có BottomNavigationView được liên kết với một NavGraph, thì đích đến mặc định của NavGraph sẽ ấn định thẻ được chọn khi khởi động ứng dụng. Tuy nhiên, bạn có thể phải ghi đè hành vi này, chẳng hạn như khi một tuỳ chọn người dùng chỉ định một thẻ ưa thích cần được tải khi khởi động ứng dụng. Ngoài ra, ứng dụng của bạn có thể cần thay đổi thẻ bắt đầu dựa trên hành vi trước đây của người dùng. Bạn có thể hỗ trợ các trường hợp này bằng cách linh động chỉ định đích đến mặc định của NavGraph.

Nên cân nhắc NavGraph này.

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

Khi biểu đồ này được tải, thuộc tính app:startDestination chỉ định rằng HomeFragment sẽ được hiển thị. Để linh động ghi đè đích đến bắt đầu, làm như sau:

  1. Trước tiên, mở rộng NavGraph bằng cách thủ công.
  2. Ghi đè đích đến bắt đầu.
  3. Cuối cùng, đính kèm biểu đồ vào NavController theo cách thủ công.

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

Giờ đây, khi ứng dụng của bạn khởi động, ShopFragment sẽ hiển thị thay vì HomeFragment.

Khi sử dụng liên kết sâu, NavController sẽ tự động tạo một ngăn xếp lui cho đích đến là liên kết sâu. Nếu người dùng di chuyển đến liên kết sâu và sau đó quay lại, thì họ sẽ đến đích đến bắt đầu vào lúc nào đó. Việc ghi đè đích đến bắt đầu bằng cách sử dụng kỹ thuật trong ví dụ trước sẽ đảm bảo rằng đích đến bắt đầu chính xác được thêm vào ngăn xếp lui được tạo.

Lưu ý rằng kỹ thuật này cũng cho phép ghi đè các khía cạnh khác của NavGraph theo yêu cầu. Phải thực hiện mọi sửa đổi với biểu đồ trước lệnh gọi đến setGraph() để đảm bảo rằng cấu trúc chính xác được sử dụng khi xử lý liên kết sâu, khôi phục trạng thái và di chuyển đến đích đến bắt đầu của biểu đồ.