Thao tác điều hướng và ngăn xếp lui

NavController có một "ngăn xếp lui" có chứa đích đến mà người dùng đã truy cập. Khi người dùng điều hướng tới các màn hình trong toàn bộ ứng dụng, NavController sẽ thêm đích đến vào ngăn xếp lui và xoá đích đến khỏi ngăn xếp lui.

Với vai trò ngăn xếp, ngăn xếp lui là một cấu trúc dữ liệu "vào sau, ra trước". Chiến lược phát hành đĩa đơn Do đó, NavController sẽ đẩy các mục lên và đẩy các mục từ đầu ngăn xếp.

Hành vi cơ bản

Dưới đây là những thông tin cốt lõi mà bạn nên cân nhắc liên quan đến hành vi của ngăn xếp lui:

  • Đích đến đầu tiên: Khi người dùng mở ứng dụng, NavController sẽ đẩy đích đến đầu tiên lên đầu ngăn xếp lui.
  • Đẩy vào ngăn xếp: Mỗi lệnh gọi NavController.navigate() sẽ đẩy đích đến nhất định lên đầu ngăn xếp.
  • Đẩy đích đến trên cùng ra: Nhấn vào Lên hoặc Quay lại để gọi phương thức NavController.navigateUp()NavController.popBackStack(). Các phương thức này sẽ đẩy đích đến trên cùng ra khỏi ngăn xếp. Vui lòng xem trang Nguyên tắc điều hướng để biết thêm thông tin về điểm khác biệt giữa tuỳ chọn LênQuay lại.

Đẩy về

Phương thức NavController.popBackStack() sẽ cố gắng đẩy đích đến hiện tại ra khỏi ngăn xếp lui rồi điều hướng tới đích đến trước. Đây là cách hiệu quả để đưa người dùng lùi lại một bước trong quá trình điều hướng. Phương thức này trả về một giá trị boolean cho biết liệu đã đẩy thành công về đích đến đó hay chưa.

Đẩy về một đích đến cụ thể

Bạn cũng có thể dùng popBackStack() nhằm điều hướng tới một đích đến cụ thể. Để làm vậy, hãy áp dụng một trong các phương thức nạp chồng. Có vài phương thức cho phép bạn truyền một mã nhận dạng vào, chẳng hạn như số nguyên id hoặc một chuỗi route. Các phương thức nạp chồng này sẽ đưa người dùng tới đích đến liên kết với một mã nhận dạng nhất định. Quan trọng là chúng giúp đẩy mọi mục trong ngăn xếp lên trên đích đến đó.

Các phương thức nạp chồng này cũng tiếp nhận giá trị boolean inclusive. Đây là giá trị xác định liệu NavController có nên đẩy đích đến được chỉ định ra khỏi ngăn xếp lui hay không sau khi điều hướng tới đó.

Hãy tham khảo ví dụ về đoạn mã ngắn sau:

navController.popBackStack(R.id.destinationId, true)

Ở ví dụ này, NavController sẽ đẩy về đích đến có mã nhận dạng bằng số nguyên destinationId. Vì giá trị của đối số inclusivetrue nên NavController cũng đẩy đích đến đã xác định khỏi ngăn xếp lui.

Xử lý lỗi không đẩy về được

Khi popBackStack() trả về false, lệnh gọi tiếp theo tới NavController.getCurrentDestination() sẽ trả về null. Như vậy tức là ứng dụng đã đẩy đích đến cuối cùng ra khỏi ngăn xếp lui. Trong trường hợp này, người dùng chỉ thấy một màn hình trống.

Lỗi này có thể xảy ra trong các trường hợp sau:

  • popBackStack() không đẩy bất kỳ mục nào ra khỏi ngăn xếp.
  • popBackStack() đã đẩy một đích đến ra khỏi ngăn xếp lui và ngăn xếp đang trống.

Để giải quyết lỗi này, bạn phải điều hướng tới một đích đến mới hoặc gọi lệnh finish() để kết thúc hoạt động của mình. Sau đây là đoạn mã minh hoạ:

kotlin

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish()
}

java

...

if (!navController.popBackStack()) {
    // Call finish() on your Activity
    finish();
}

Đẩy tới một đích đến

Để xoá đích đến khỏi ngăn xếp lui khi điều hướng từ đích đến này sang đích đến khác, hãy thêm đối số popUpTo() vào lệnh gọi hàm navigate() liên kết. popUpTo() yêu cầu thư viện Navigation xoá một số đích đến khỏi ngăn xếp lui để thực hiện lệnh gọi đến navigate(). Giá trị tham số là mã nhận dạng của một đích đến trong ngăn xếp lui. Giá trị nhận dạng có thể là số nguyên id hoặc chuỗi route.

Bạn có thể thêm một đối số cho tham số inclusive có giá trị true nhằm cho biết rằng đích đến mà bạn đã chỉ định trong popUpTo() cũng sẽ bị đẩy ra khỏi ngăn xếp lui.

Để triển khai việc này theo phương thức lập trình, hãy truyền popUpTo() đến navigate() dưới dạng NavOptions sau khi đã đặt inclusive thành true. Cách này có tác dụng trong cả Compose và Khung hiển thị.

Lưu trạng thái khi đẩy ra

Khi sử dụng popUpTo để điều hướng tới một đích đến, bạn có thể lưu ngăn xếp lui và trạng thái của mọi đích đến bị kéo ra khỏi ngăn xếp lui. Bạn có thể sau đó khôi phục ngăn xếp lui và đích đến khi điều hướng tới đích đến đó sau này. Điều này cho phép bạn duy trì trạng thái cho một đích đến nhất định và nhiều ngăn xếp lui.

Để thực hiện việc này theo phương thức lập trình, hãy chỉ định saveState = true khi thêm popUpTo vào các tùy chọn điều hướng của mình.

Bạn cũng có thể chỉ định restoreState = true trong các tuỳ chọn điều hướng để tự động khôi phục ngăn xếp lui và trạng thái liên kết với đích.

Ví dụ:

navController.navigate(
    route = route,
    navOptions =  navOptions {
        popUpTo<A>{ saveState = true }
        restoreState = true
    }
)

Để bật tính năng lưu và khôi phục trạng thái trong XML, hãy xác định popUpToSaveStatetruerestoreState dưới dạng true lần lượt trong action được liên kết.

Ví dụ cho XML

Dưới đây là một ví dụ về popUpTo trong XML có sử dụng một thao tác:

<action
  android:id="@+id/action_a_to_b"
  app:destination="@id/b"
  app:popUpTo="@+id/a"
  app:popUpToInclusive="true"
  app:restoreState=”true”
  app:popUpToSaveState="true"/>

Ví dụ về Compose

Dưới đây là một ví dụ hoàn chỉnh tương tự trong Compose:

@Composable
fun MyAppNavHost(
    modifier: Modifier = Modifier,
    navController: NavHostController = rememberNavController(),
    startDestination: Any = A
) {
    NavHost(
        modifier = modifier,
        navController = navController,
        startDestination = startDestination
    ) {
        composable<A> {
            DestinationA(
                onNavigateToB = {
                // Pop everything up to, and including, the A destination off
                // the back stack, saving the back stack and the state of its
                // destinations.
                // Then restore any previous back stack state associated with
                // the B destination.
                // Finally navigate to the B destination.
                    navController.navigate(route = B) {
                        popUpTo<A> {
                            inclusive = true
                            saveState = true
                        }
                        restoreState = true
                    }
                },
            )
        }
        composable<B> { DestinationB(/* ... */) }
    }
}

@Composable
fun DestinationA(onNavigateToB: () -> Unit) {
    Button(onClick = onNavigateToB) {
        Text("Go to A")
    }
}

Cụ thể hơn, bạn có thể thay đổi cách gọi lệnh NavController.navigate() theo các phương thức sau:

// Pop everything up to the destination_a destination off the back stack before
// navigating to the "destination_b" destination
navController.navigate("destination_b") {
    popUpTo("destination_a")
}

// Pop everything up to and including the "destination_a" destination off
// the back stack before navigating to the "destination_b" destination
navController.navigate("destination_b") {
    popUpTo("destination_a") { inclusive = true }
}

// Navigate to the "search” destination only if we’re not already on
// the "search" destination, avoiding multiple copies on the top of the
// back stack
navController.navigate("search") {
    launchSingleTop = true
}

Để biết thông tin chung về cách truyền tuỳ chọn tới NavController.navigate(), hãy xem Hướng dẫn cách sử dụng tuỳ chọn.

Đẩy ra bằng thao tác

Khi điều hướng bằng thao tác, bạn có thể tuỳ ý đẩy các đích đến khác ra khỏi ngăn xếp lui. Ví dụ: nếu ứng dụng có luồng đăng nhập ban đầu, thì khi người dùng đã đăng nhập, bạn nên đẩy mọi đích đến liên quan tới hoạt động đăng nhập ra khỏi ngăn xếp lui để nút Quay lại không đưa người dùng trở về luồng đăng nhập đó.

Đọc thêm

Để biết thêm thông tin, hãy đọc các trang sau:

  • Luồng điều hướng vòng tròn: Tìm hiểu cách bạn có thể tránh tình trạng ngăn xếp lui bị quá tải trong trường hợp luồng điều hướng đi theo vòng tròn.
  • Đích đến của hộp thoại: Tìm hiểu cách các đích đến của hộp thoại đưa ra những điểm cần cân nhắc riêng cho việc quản lý ngăn xếp lui.