Navigation Compose là một thư viện Jetpack cho điều hướng khai báo trong các ứng dụng Android được xây dựng trên Jetpack Compose. Thay vì FragmentManager hoặc điều hướng dựa trên Intent, Navigation Compose cung cấp một đồ thị tuyến đường duy nhất được quản lý thông qua NavController và NavHost. Theo Google I/O (2025), Navigation Compose là phương pháp điều hướng được khuyến nghị cho các ứng dụng Compose, được sử dụng trong hơn 70% các dự án mới. Thư viện hỗ trợ truyền tham số có kiểu, liên kết sâu, hoạt ảnh chuyển tiếp và tích hợp với ViewModel thông qua SavedStateHandle.
Những điểm chính
Navigation Compose là một thư viện trong bộ Jetpack cung cấp khung điều hướng cho các ứng dụng Compose. Thư viện dựa trên các nguyên tắc tương tự như Thành phần Điều hướng cho hệ thống View, nhưng được điều chỉnh cho phù hợp với bản chất khai báo của Compose: thay vì FragmentTransaction, các hàm composable được sử dụng và đồ thị điều hướng được xây dựng thông qua Kotlin DSL.
Sự khác biệt chính giữa Navigation Compose và điều hướng cổ điển là không có FragmentManager. Mỗi màn hình là một hàm composable được hiển thị trong NavHost khi tuyến đường khớp. Ngăn xếp quay lại lưu trữ không phải Fragment, mà là một bản ghi với tuyến đường, tham số và trạng thái. Điều này đơn giản hóa kiến trúc và loại bỏ xung đột vòng đời điển hình của điều hướng dựa trên Fragment.
Theo Google (2025), Navigation Compose đã chuyển từ thử nghiệm sang ổn định và là một phần của Jetpack kể từ phiên bản 2.8.0. Thư viện hỗ trợ Material3, điều hướng an toàn kiểu (thông qua Kotlin Serialization), đồ thị lồng nhau và mô-đun hóa. Hạn chế duy nhất là thư viện không hỗ trợ ngăn xếp quay lại đa luồng cho BottomNavigation mà không cấu hình thủ công, mặc dù Google đang làm việc về điều này.
Kiến trúc của Navigation Compose xoay quanh ba thực thể: NavController (quản lý ngăn xếp), NavHost (vùng chứa đồ thị) và NavDestination (tuyến đường riêng lẻ với composable). Tương tác giữa chúng là khai báo: nhà phát triển mô tả các tuyến đường và tham số, và thư viện xử lý các trạng thái tải, bảo quản và khôi phục.
NavController là phần tử trung tâm của Navigation Compose, quản lý ngăn xếp điều hướng. Nó được tạo thông qua rememberNavController() và được truyền cho NavHost. NavController lưu trữ ngăn xếp quay lại, điểm vào hiện tại và hỗ trợ các hành động trì hoãn (deeplink sau khi khởi tạo đồ thị).
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("profile/{userId}") { backStackEntry ->
ProfileScreen(
userId = backStackEntry.arguments?.getString("userId") ?: ""
)
}
}
}
Các phương thức chính của NavController: navigate(route) — điều hướng đến một tuyến đường, popBackStack() — quay lại màn hình trước đó, navigateAndClear(route) — điều hướng và xóa ngăn xếp. NavOptions chỉ định hành vi: launchSingleTop ngăn trùng lặp, popUpTo xóa ngăn xếp đến tuyến đường được chỉ định, restoreState khôi phục trạng thái trước đó.
Để truy cập NavController từ các hàm composable lồng nhau sâu, hãy sử dụng NavHostController thông qua CompositionLocal. LocalNavController được cung cấp trong ScopedNavController bên trong NavHost. Bên ngoài NavHost (ví dụ: trong BottomNavigation), bộ điều khiển được truyền qua tham số hoặc ViewModel.
NavHost là một vùng chứa composable kết nối NavController với đồ thị tuyến đường. Mỗi tuyến đường được khai báo thông qua composable(route, arguments, deepLinks), trong đó route là một chuỗi tuyến đường với các trình giữ chỗ {param} tùy chọn. Khi tuyến đường hiện tại khớp, NavHost hiển thị khối composable tương ứng.
Đồ thị tuyến đường được xây dựng theo phân cấp: các đồ thị có thể được lồng nhau thông qua navigation() để nhóm các tuyến đường trong một mô-đun. Các đồ thị lồng nhau có startDestination riêng và được kết hợp dưới một tiền tố tuyến đường chung. Điều này cho phép tổ chức kiến trúc mô-đun nơi mỗi mô-đun tính năng đăng ký đồ thị con của riêng mình.
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
navigation(
route = "auth",
startDestination = "auth/login"
) {
composable("auth/login") { LoginScreen(navController) }
composable("auth/register") { RegisterScreen(navController) }
}
}
NavHost tự động xử lý nút Quay lại hệ thống (back press) thông qua LocalBackDispatcher. Trong Material3 Scaffold, nó mặc định lấy NavController để BottomNavigation hoạt động chính xác. NavHost tạo lại composable khi tuyến đường thay đổi, nhưng bảo toàn trạng thái thông qua rememberSaveable cho các trường nhập liệu và cuộn.
Navigation Compose hỗ trợ truyền tham số có kiểu giữa các màn hình thông qua tham số tuyến đường và NavType. Các tham số được chỉ định trong tuyến đường dưới dạng {paramName} với kiểu được chỉ định thông qua arguments trong composable(). NavType hỗ trợ String, Int, Long, Float, Boolean, Parcelable và Serializable.
| Loại tham số | NavType | Ví dụ tuyến đường |
|---|---|---|
| String | NavType.StringType | "profile/{name}" |
| Int | NavType.IntType | "item/{id}" |
| Boolean | NavType.BoolType | "settings?enabled={flag}" |
| Parcelable | NavType.ParcelableType | "details/{item}" |
| Float | NavType.FloatType | "map?lat={lat}&lng={lng}" |
Các tham số được trích xuất từ NavBackStackEntry thông qua arguments?.getType(key). Đối với tham số bắt buộc, hãy sử dụng defaultValue, đối với tùy chọn — nullable. Hỗ trợ Parcelable chỉ hoạt động với Kotlin Parcelize hoặc thư viện kotlinx.parcelize. Đối với các đối tượng phức tạp, nên truyền ID và tải dữ liệu qua ViewModel, thay vì tuần tự hóa toàn bộ đối tượng.
Kể từ Navigation 2.8.0, điều hướng an toàn kiểu với Kotlin Serialization đã được giới thiệu: các tuyến đường được định nghĩa là lớp dữ liệu và tham số là trường. Điều này thay thế các tuyến đường chuỗi bằng các đối tượng có kiểu và loại bỏ lỗi trong tên tuyến đường. Việc di chuyển yêu cầu plugin Kotlin Serialization và phụ thuộc navigation-compose-typesafe.
@Serializable
/* sealed class Route */
sealed class ProfileRoute(val route: String) {
data object Home : ProfileRoute("home")
data class Profile(val userId: String) : ProfileRoute("profile/{userId}")
}
Liên kết sâu là cơ chế điều hướng cho phép mở một màn hình ứng dụng cụ thể thông qua URL hoặc intent-filter. Trong Navigation Compose, liên kết sâu được cấu hình thông qua tham số deepLinks trong composable() và được xử lý tự động khi URI khớp với mẫu.
Liên kết sâu được chỉ định dưới dạng danh sách UriPattern: "https://example.com/profile/{userId}". Các tham số URI tự động được ánh xạ đến các tham số tuyến đường. NavController xử lý các liên kết sâu khi khởi động ứng dụng (thông qua intent) và trong quá trình hoạt động (thông qua liên kết sâu ngầm). Để xử lý các liên kết sâu đang chờ xử lý, hãy sử dụng handleDeepLink() trong NavController sau khi khởi tạo đồ thị.
Theo Google, liên kết sâu được khuyến nghị cho: thông báo đẩy (Firebase Dynamic Links), xác minh email, chia sẻ nội dung và điều hướng chéo từ các liên kết web. Đối với Android 12+, Digital Asset Links được sử dụng để xác minh quyền hạn của liên kết sâu. AndroidManifest.xml phải chứa intent-filter với autoVerify="true" để mở liên kết mà không cần hộp thoại.
composable(
route = "profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType }),
deepLinks = listOf(
navDeepLink { uriPattern = "https://example.com/profile/{userId}" }
)
) { backStackEntry ->
ProfileScreen(userId = backStackEntry.arguments?.getString("userId") ?: "")
}
Hạn chế của liên kết sâu trong Navigation Compose: thư viện không hỗ trợ liên kết sâu trì hoãn — liên kết sâu chỉ được xử lý sau khi NavHost đã xây dựng hoàn chỉnh đồ thị. Nếu liên kết sâu đến trước khi khởi tạo đồ thị, nó phải được trì hoãn thông qua intent?.data và xử lý trong LaunchedEffect. Đối với Firebase Dynamic Links, hãy sử dụng Firebase Dynamic Links SDK cùng với Navigation Compose.
Navigation Compose hỗ trợ hoạt ảnh chuyển tiếp thông qua các tham số enterTransition, exitTransition, popEnterTransition và popExitTransition trong composable(). Hoạt ảnh được triển khai bằng Compose Animation API: fadeIn, slideInHorizontally, expandIn và các loại khác. Theo mặc định, hoạt ảnh bị tắt — các màn hình được thay thế ngay lập tức.
Các kịch bản hoạt ảnh điển hình: slideInHorizontally cho điều hướng tiến (màn hình trượt vào từ bên phải), slideOutHorizontally cho quay lại (màn hình trượt ra bên phải). Đối với BottomNavigation, hoạt ảnh mờ dần không trượt được sử dụng phổ biến hơn. Hoạt ảnh được thiết lập thông qua NavHost và áp dụng cho tất cả các composable trừ khi có hoạt ảnh riêng được chỉ định.
NavHost(
navController = navController,
startDestination = "home",
enterTransition = { slideInHorizontally() + fadeIn() },
exitTransition = { slideOutHorizontally() + fadeOut() },
popEnterTransition = { fadeIn() },
popExitTransition = { slideOutHorizontally() + fadeOut() }
) { /* composable */ }
Hoạt ảnh có thể được ghi đè cho từng composable riêng lẻ bằng cách truyền tham số hoạt ảnh trực tiếp trong composable(). Điều quan trọng là hoạt ảnh không được xung đột với hoạt ảnh quay lại hệ thống. Đối với chuyển tiếp phần tử dùng chung, cần có thư viện accompanist-navigation-animation hoặc triển khai tùy chỉnh thông qua Modifier.graphicsLayer. Theo Android Developers (2025), 80% ứng dụng sản xuất sử dụng hoạt ảnh trượt ngang cho điều hướng tiêu chuẩn.
Câu hỏi thường gặp
Navigation Compose hoạt động không cần Fragment, sử dụng các hàm composable và Kotlin DSL cho đồ thị. Thành phần Điều hướng (View) dựa trên FragmentManager và đồ thị XML. Phiên bản Compose đơn giản hơn, nhanh hơn và không có vòng đời Fragment. Thành phần Điều hướng cho View chỉ phù hợp cho các ứng dụng kết hợp.
Nên truyền ID của đối tượng và tải dữ liệu qua ViewModel với SavedStateHandle. Nếu đối tượng đơn giản, hãy sử dụng Parcelable thông qua kotlinx.parcelize. Truyền trực tiếp các đối tượng lớn qua tham số (Bundle) bị giới hạn ở ~1 MB và có thể gây ra TransactionTooLargeException.
Có, thông qua hàm navigation(route, startDestination) bên trong NavHost. Các đồ thị lồng nhau có startDestination riêng và được kết hợp dưới một tiền tố tuyến đường chung. Điều này cho phép tổ chức kiến trúc mô-đun với các đồ thị riêng biệt cho mỗi mô-đun tính năng.
NavController tự động xử lý thao tác nhấn quay lại thông qua BackHandler từ Compose. Gọi navController.popBackStack() khi nhấn Quay lại. Để xử lý tùy chỉnh (xác nhận thoát), hãy sử dụng BackHandler(enabled = condition) { callback } trước khi gọi popBackStack().
Hiện tại, Navigation Compose không được hỗ trợ trong Compose Multiplatform. Đối với phần iOS của các dự án đa nền tảng, hãy sử dụng Voyager hoặc Decompose. Google đang làm việc hỗ trợ KMP, nhưng chưa có lịch phát hành. Đối với các dự án chỉ Android, Navigation Compose là lựa chọn duy nhất được khuyến nghị.
Tóm tắ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