NavController là thành phần trung tâm của thư viện Navigation Compose, quản lý ngăn xếp điều hướng và trạng thái back stack trong các ứng dụng Android. Thông qua NavController, các chuyển tiếp giữa màn hình, quay lại trang trước và truyền dữ liệu giữa các tuyến đường được thực hiện. Theo Android Developers (2025), NavController là yếu tố bắt buộc của mọi ứng dụng Compose có nhiều hơn một màn hình. Bộ điều khiển được tạo qua rememberNavController(), truyền đến NavHost và có sẵn để gọi navigate() từ bất kỳ điểm nào trong thành phần. Hỗ trợ tích hợp SavedStateHandle tự động lưu trạng thái ViewModel trong quá trình cấu hình lại.
Những Điểm Chính
NavController là một lớp từ thư viện Navigation Compose triển khai bộ điều khiển điều hướng cho các ứng dụng Compose. NavController quản lý ngăn xếp NavBackStackEntry, nơi mỗi mục chứa tuyến đường, đối số và trạng thái màn hình. Bộ điều khiển hỗ trợ các thao tác điều hướng cơ bản: chuyển tiếp, quay lại, thay thế và dọn dẹp.
Không giống như hệ thống View, nơi điều hướng diễn ra qua FragmentManager hoặc Intent, NavController hoạt động độc quyền trong ngữ cảnh Compose. Back stack được lưu trữ dưới dạng đồ thị NavDestination thay vì ngăn xếp Fragment. Điều này loại bỏ chi phí tạo và hủy Fragment và đơn giản hóa việc kiểm thử — NavController có thể được mô phỏng qua TestNavHostController.
NavController được liên kết chặt chẽ với NavHost — một vùng chứa hiển thị màn hình hiện tại từ đồ thị. Nếu không có NavHost, NavController không thể hiển thị các hàm composable nhưng vẫn giữ khả năng quản lý ngăn xếp. Trong kiến trúc điển hình, NavController được tạo ở cấp Activity hoặc composable chính và được truyền xuống cây thành phần qua các tham số.
Theo Google, NavController đã trải qua nhiều bản phát hành lớn. Phiên bản 2.8.0 đã thêm Type-Safe Navigation, phiên bản 2.9.0 đã thêm hỗ trợ predictive back gesture (Android 14+). Bộ điều khiển tương thích với Material3 Scaffold và BottomNavigation. Đối với các dự án đa mô-đun, NavController được truyền qua DI (Hilt/Koin) hoặc tham số hàm tạo.
NavController được tạo qua hàm composable rememberNavController(). Hàm trả về một thể hiện của NavHostController (lớp con của NavController) gắn với vòng đời của composable hiện tại. Khi rời khỏi thành phần, bộ điều khiển được dọn dẹp. Để bảo toàn bộ điều khiển trong quá trình cấu hình lại, hãy sử dụng rememberSaveable hoặc ViewModel.
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
composable("details") { DetailsScreen(navController) }
}
}
Cấu hình NavController bao gồm: NavHostController (chính), TestNavHostController (kiểm thử) và ScopedNavController (con cho đồ thị lồng nhau). Đối với BottomNavigation, NavController phải là duy nhất cho toàn bộ ứng dụng — tạo bộ điều khiển mới trong mỗi tab sẽ dẫn đến mất ngăn xếp. Để truyền bộ điều khiển đến các màn hình lồng nhau, hãy sử dụng tham số hàm thay vì CompositionLocalProvider để duy trì tính dễ đọc.
Để kiểm thử điều hướng, hãy sử dụng TestNavHostController với compose-test-rule. Bộ điều khiển cho phép đặt tuyến đường ban đầu và xác minh rằng navigate() đã kích hoạt chuyển tiếp mong đợi. Kiểm thử NavController không yêu cầu trình giả lập — nó hoạt động với các bộ so khớp Semantics của Compose Test.
Phương thức navigate(route: String) là cơ chế điều hướng chính trong NavController. Nó chấp nhận một chuỗi tuyến đường, NavOptions tùy chọn và Navigator.Extras. NavOptions kiểm soát hành vi chuyển tiếp: launchSingleTop (không trùng lặp tuyến đường trong ngăn xếp), popUpTo (dọn dẹp ngăn xếp đến một tuyến đường), restoreState (khôi phục trạng thái trước đó).
NavOptions được đặt qua cú pháp builder: NavOptionsBuilder. Các tham số chính: popUpTo (tuyến đường + inclusive/saveState), launchSingleTop (Boolean, true — không tạo bản sao), restoreState (khôi phục trạng thái khi quay lại). Nếu không có popUpTo, mỗi navigate() thêm một mục vào ngăn xếp, dẫn đến tích tụ back stack và hành vi sai của nút Back.
navController.navigate("profile/42") {
popUpTo("main") { saveState = true }
launchSingleTop = true
restoreState = true
}
Navigator.Extras cho phép truyền dữ liệu bổ sung không thuộc tuyến đường: các phần tử chia sẻ cho hoạt ảnh, cờ Intent, gói Pac-Man. Extras hiếm khi được sử dụng — chủ yếu để tích hợp với Accompanist Animation hoặc Navigators tùy chỉnh. Đối với hầu hết các kịch bản, một chuỗi tuyến đường và NavOptions là đủ.
popBackStack() là phương thức để quay lại màn hình trước. Không có đối số, nó xóa mục trên cùng của ngăn xếp và trả về true nếu việc xóa thành công. Nếu ngăn xếp trống, phương thức trả về false và Activity đóng lại (tương tự super.onBackPressed()).
Phiên bản nạp chồng popBackStack(route: String, inclusive: Boolean) xóa tất cả các mục cho đến tuyến đường được chỉ định. Nếu inclusive = true, chính tuyến đường được chỉ định cũng bị xóa. Phương thức trả về Boolean — true nếu tìm thấy và xóa các mục. Phiên bản inclusive hữu ích cho các kịch bản “thoát đến màn hình gốc” sau khi ủy quyền hoặc hoàn tất đơn hàng.
| Phương thức | Mô tả | Ví dụ |
|---|---|---|
| popBackStack() | Quay lại một màn hình | navController.popBackStack() |
| popBackStack(route, false) | Dọn đến route (route giữ lại) | popBackStack(“home”, false) |
| popBackStack(route, true) | Dọn đến và bao gồm route | popBackStack(“home”, true) |
| navigate(route) { popUpTo(route) { inclusive = true } } | Chuyển tiếp với dọn dẹp hoàn toàn | navigate(“login”) { popUpTo(0) { inclusive = true } } |
Để xử lý nút Back hệ thống (hardware back button), hãy sử dụng BackHandler từ Compose. BackHandler chấp nhận enabled và onBack — một callback được gọi khi nhấn. Đối với Android 14+, PredictiveBackGesture được sử dụng, tích hợp qua NavController từ phiên bản 2.9.0. Predictive back thêm hoạt ảnh xem trước quay lại.
SavedStateHandle là một cơ chế bảo toàn trạng thái ViewModel trong quá trình điều hướng và cấu hình lại. NavController tự động cung cấp SavedStateHandle cho mỗi NavBackStackEntry. Qua SavedStateHandle, ViewModel lưu trữ trạng thái màn hình và khôi phục khi quay lại (restoreState = true).
Trong Navigation Compose, SavedStateHandle được sử dụng cùng với ViewModel: ViewModel được khởi tạo qua SavedStateHandle, được truyền từ backStackEntry. Khi điều hướng đến màn hình khác và quay lại (với restoreState), ViewModel nhận trạng thái đã lưu thay vì được tạo mới. Điều này rất quan trọng đối với các màn hình có nhập dữ liệu, bộ lọc hoặc cuộn.
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userId: String = savedStateHandle.get<String>("userId") ?: ""
var searchQuery by savedStateHandle.getStateFlow("search", "")
.collectAsState()
}
SavedStateHandle hỗ trợ các kiểu nguyên thủy, String, Bundle và Parcelable. Đối với các đối tượng phức tạp, chỉ lưu ID và tải dữ liệu đầy đủ từ kho lưu trữ. Giới hạn của SavedStateHandle là khoảng 1 MB, vượt quá sẽ gây ra TransactionTooLargeException. Đối với khối lượng lớn, hãy sử dụng Room hoặc DataStore thay vì lưu trong handle.
Quan trọng: SavedStateHandle chỉ bảo toàn trạng thái khi restoreState = true được sử dụng trong NavOptions. Nếu restoreState không được chỉ định, ViewModel được tạo mới với các giá trị mặc định khi quay lại. Đối với chuyển đổi BottomNavigation với restoreState, NavController bảo toàn trạng thái của mỗi tab và khôi phục khi chọn lại.
currentBackStackEntryAsState() là một hàm trả về State<NavBackStackEntry?>, cập nhật mỗi khi tuyến đường hiện tại thay đổi. Đây là cơ chế chính để đồng bộ hóa giao diện người dùng với điều hướng: BottomNavigation làm nổi bật mục đang hoạt động, Toolbar cập nhật tiêu đề, Drawer đóng khi chuyển tiếp.
Hàm hoạt động qua snapshotFlow và collectAsState: khi back stack thay đổi, Compose tái tạo các phần tử đã đăng ký. Quan trọng: currentBackStackEntryAsState() chỉ cập nhật sau khi hoạt ảnh chuyển tiếp hoàn tất. Để cập nhật ngay lập tức, hãy sử dụng currentDestination, thay đổi đồng bộ với navigate() nhưng không hỗ trợ trạng thái.
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Text(
text = when (currentRoute) {
"home" -> "Home"
"profile" -> "Profile"
else -> ""
}
)
Để truy cập các đối số của tuyến đường hiện tại, hãy sử dụng navBackStackEntry?.arguments. Điều này thuận tiện trong BottomNavigation: selectedItem được tính dựa trên currentRoute. Để gỡ lỗi điều hướng, hãy sử dụng NavController.addOnDestinationChangedListener() ghi lại mỗi chuyển tiếp. Trong sản xuất, tránh đăng ký bên trong số lượng lớn composable — hãy tạo một nguồn duy nhất trong ViewModel và truyền State đến giao diện người dùng.
Câu Hỏi Thường Gặp
Về mặt kỹ thuật là có, nhưng không được khuyến khích. Một NavController duy nhất đảm bảo back stack nhất quán và đơn giản hóa việc gỡ lỗi. Nhiều bộ điều khiển chỉ hợp lý cho các đồ thị lồng nhau với điều hướng riêng biệt (ví dụ: modal bottom sheet với ngăn xếp riêng).
Truyền NavController đến ViewModel qua hàm tạo hoặc DI. Tuy nhiên, tốt hơn là chỉ truyền các hàm callback (onNavigate, onBack) thay vì chính NavController — điều này đơn giản hóa việc kiểm thử. Đối với sự kiện, hãy sử dụng Channel<NavEvent> trong ViewModel và thu thập trong giao diện người dùng.
Vấn đề là vòng đời: nếu NavController chưa được khởi tạo (NavHost chưa được xây dựng), navigate() bị bỏ qua. Sử dụng LaunchedEffect để gọi điều hướng sau khi tải dữ liệu, không phải bên trong một coroutine có vòng đời tùy ý.
Gọi navController.navigate(“target”) { popUpTo(0) { inclusive = true } }. Tham số popUpTo(0) dọn sạch ngăn xếp hoàn toàn, inclusive = true cũng xóa mục bắt đầu. Cờ launchSingleTop = true ngăn chặn các tuyến đường trùng lặp.
NavHostController là một lớp con của NavController với các phương thức bổ sung cho NavHost (ví dụ: setOnBackStackChangedListener). NavController là lớp cơ sở có thể được sử dụng bên ngoài NavHost để quản lý ngăn xếp theo chương trình. Trong hầu hết các trường hợp, NavHostController được sử dụng.
Tổng Kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm