NavHost — nó là gì, xây dựng đồ thị và tuyến đường trong Jetpack Compose

Tác giả: IT Sectr Đã đăng: 2026-06-30 Thời gian đọc: 7 phút

NavHost là vùng chứa composable đóng vai trò là điểm vào cho đồ thị điều hướng trong Jetpack Compose. Nó liên kết NavController với một tập hợp các tuyến đường và hiển thị màn hình hiện tại dựa trên trạng thái của ngăn xếp quay lại. Theo Android Developers (2025), NavHost là thành phần bắt buộc cho bất kỳ ứng dụng Compose nào có điều hướng. Bên trong NavHost, các tuyến đường composable được đăng ký với các đối số tùy chọn, deep link và hoạt ảnh. Mỗi tuyến đường là một hàm composable thông thường nhận NavBackStackEntry với dữ liệu chuyển tiếp. NavHost tự động xử lý nút quay lại, lưu trạng thái và khôi phục khi cấu hình lại.

Những Điểm Chính

  • NavHost — vùng chứa composable liên kết NavController với đồ thị tuyến đường và hiển thị màn hình hiện tại
  • composable() — hàm đăng ký tuyến đường với đường dẫn, đối số, deep link và hoạt ảnh chuyển tiếp
  • startDestination — tuyến đường ban đầu được mở khi tạo NavHost
  • Đối số được định nghĩa qua navArgument với NavType để truyền dữ liệu có kiểu giữa các màn hình
  • Hoạt ảnh được cấu hình toàn cục hoặc riêng lẻ qua các tham số enterTransition, exitTransition

NavHost trong Jetpack Compose là gì?

NavHost là một hàm composable cung cấp vùng chứa để hiển thị màn hình điều hướng hiện tại. NavHost nhận NavController, startDestination và đồ thị tuyến đường được xây dựng qua Kotlin DSL. Khi tuyến đường hiện tại thay đổi, NavHost chuyển đổi composable được hiển thị với hoạt ảnh đã chỉ định.

NavHost hoạt động như một bộ chuyển đổi màn hình: nó theo dõi NavBackStackEntry hiện tại từ NavController và hiển thị khối composable tương ứng. Mỗi màn hình là một hàm composable độc lập nhận NavBackStackEntry với các đối số tuyến đường. Tất cả các màn hình tồn tại trong một cây thành phần duy nhất, nhưng NavHost chỉ hiển thị một màn hình tại một thời điểm, ẩn các màn hình khác qua hoạt ảnh.

Không giống như FragmentManager, NavHost không tạo Fragment cho mỗi màn hình. Toàn bộ vòng đời được quản lý qua CompositionLifecycle — các hàm composable không có onStart/onResume, do đó LaunchedEffect và DisposableEffect được sử dụng cho các tác dụng phụ. NavHost tự động đăng ký NavController và kết hợp lại UI khi tuyến đường thay đổi.

Theo Google, NavHost là API ổn định từ Navigation 2.4.0. Bắt đầu từ 2.8.0, NavHost hỗ trợ điều hướng Type-Safe qua Kotlin Serialization, thay thế các tuyến đường chuỗi bằng các lớp dữ liệu. NavHost cũng hỗ trợ đồ thị lồng nhau, cho phép tổ chức điều hướng theo mô-đun.

NavHost được tạo với hai tham số bắt buộc: navController (một thể hiện của NavHostController) và startDestination (chuỗi tuyến đường của màn hình đầu tiên). Tham số thứ ba là khối xây dựng nơi tất cả các tuyến đường được đăng ký qua composable(), navigation() và dialog().

kotlin
@Composable
fun AppNavHost(navController: NavHostController) {
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") { HomeScreen(navController) }
        composable("settings") { SettingsScreen(navController) }
    }
}

startDestination là tuyến đường được mở khi NavHost khởi chạy lần đầu tiên. Nếu ngăn xếp quay lại trống, NavHost tự động thêm startDestination vào ngăn xếp. Khi cấu hình lại (xoay màn hình), NavHost khôi phục tuyến đường cuối cùng từ savedState, không phải startDestination.

Đối với BottomNavigation, startDestination là một trong các tuyến đường của bảng điều khiển phía dưới. Các tuyến đường bảng điều khiển còn lại được thêm dưới dạng các mục composable riêng biệt. NavHost nên được đặt bên trong Scaffold.content — nơi nội dung chính của ứng dụng được hiển thị. NavHost chiếm toàn bộ chiều cao khả dụng trừ TopAppBar và BottomNavigation.

Đăng ký tuyến đường qua composable

Hàm composable(route, arguments, deepLinks, enterTransition, exitTransition, content) đăng ký một tuyến đường trong đồ thị NavHost. Tham số route là một chuỗi mô tả đường dẫn với các trình giữ chỗ tùy chọn dạng {paramName}. Trình giữ chỗ được thay thế bằng giá trị thực trong quá trình điều hướng.

Khối content của composable nhận NavBackStackEntry từ đó các đối số được trích xuất. Hàm composable của màn hình chỉ được hiển thị khi tuyến đường NavController hiện tại khớp với tuyến đường. Nếu không khớp, composable bị xóa khỏi thành phần, nhưng trạng thái của nó có thể được bảo toàn qua rememberSaveable hoặc ViewModel với SavedStateHandle.

kotlin
composable(
    route = "article/{articleId}",
    arguments = listOf(navArgument("articleId") {
        type = NavType.IntType
        defaultValue = 0
    }),
    deepLinks = listOf(navDeepLink { uriPattern = "https://app.example/article/{articleId}" })
) { backStackEntry ->
    val articleId = backStackEntry.arguments?.getInt("articleId") ?: 0
    ArticleScreen(articleId = articleId)
}

Số lượng mục composable bên trong NavHost có thể từ vài đến hàng trăm. Đối với các ứng dụng lớn, các tuyến đường được chia thành các mô-đun và kết nối qua đồ thị lồng nhau. Mỗi composable có thể có cài đặt hoạt ảnh, deep link và đối số riêng.

Đối số và tham số tuyến đường có kiểu

Các đối số tuyến đường được định nghĩa qua tham số arguments: List<NamedNavArgument> trong composable(). Mỗi đối số được định nghĩa qua navArgument(name) { type; defaultValue }. NavType xác định loại đối số: StringType, IntType, LongType, FloatType, BoolType, ParcelableType và ReferenceType.

Tham số tuyến đườngVí dụ tuyến đườngNavType
Đường dẫn (path)"user/{id}"NavType.IntType
Truy vấn (query)"search?q={query}"NavType.StringType
Tùy chọn"details/{id}?tab={tab}"StringType + defaultValue=""
Parcelable"checkout/{order}"NavType.ParcelableType

Các đối số được trích xuất từ NavBackStackEntry qua arguments?.getInt("id"). Đối với đối số bắt buộc, defaultValue có thể được bỏ qua — NavType sẽ sử dụng null. Đối với đối số tùy chọn, defaultValue phải được đặt, nếu không điều hướng sẽ ném ngoại lệ nếu tham số bị thiếu.

Từ Navigation 2.8.0, điều hướng Type-Safe được khuyến nghị: định nghĩa một sealed class hoặc data class cho các tuyến đường với Kotlin Serialization. Thay vì tuyến đường chuỗi, hãy sử dụng composable<RouteType> { backStackEntry -> }. Điều này loại bỏ lỗi chính tả trong tuyến đường và tự động tạo NavType cho các đối số. Để di chuyển, hãy thêm phụ thuộc navigation-compose-typesafe và plugin Kotlin Serialization.

Đồ thị điều hướng lồng nhau

nested graphs — cơ chế nhóm các tuyến đường bên trong NavHost bằng hàm navigation(route, startDestination). Đồ thị lồng nhau có tiền tố tuyến đường và startDestination riêng, và tất cả các tuyến đường của nó có thể truy cập qua tiền tố. Đồ thị lồng nhau được sử dụng cho kiến trúc mô-đun, nơi mỗi mô-đun tính năng đăng ký đồ thị con riêng của nó.

Lợi ích của đồ thị lồng nhau: cô lập tuyến đường trong mô-đun, ngăn xếp quay lại thống nhất cho một nhóm màn hình và khả năng điều hướng theo tiền tố mà không để lộ cấu trúc bên trong. Ví dụ, đồ thị "auth" chứa "auth/login" và "auth/register". Điều hướng có thể thực hiện qua tuyến đường đầy đủ hoặc qua tiền tố với chuyển hướng đến startDestination.

kotlin
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) }
    }
}

Đồ thị lồng nhau hỗ trợ truyền đối số ở cấp đồ thị: các tham số được khai báo trong tuyến đường của đồ thị được truyền đến tất cả các tuyến đường bên trong. Để xóa đồ thị lồng nhau, hãy sử dụng popBackStack(route) — nó sẽ xóa tất cả các mục bên trong. Đồ thị lồng nhau không có giới hạn độ sâu, nhưng không nên quá 3 cấp để dễ đọc.

Tùy chỉnh hoạt ảnh và chuyển tiếp

NavHost hỗ trợ hoạt ảnh chuyển tiếp giữa các tuyến đường composable qua các tham số enterTransition, exitTransition, popEnterTransitionpopExitTransition. Hoạt ảnh được đặt một lần cho NavHost và áp dụng cho tất cả các tuyến đường, hoặc riêng lẻ cho mỗi composable. Theo mặc định, hoạt ảnh bị tắt.

Cấu hình điển hình: enterTransition = slideInHorizontally(initialOffsetX = { it }) — màn hình trượt vào từ bên phải; exitTransition = slideOutHorizontally(targetOffsetX = { -it }) — màn hình trượt ra bên trái. Đối với hoạt ảnh pop, hướng được phản chiếu: màn hình trượt vào từ bên trái và trượt ra bên phải. Đối với BottomNavigation, fadeIn/fadeOut được sử dụng mà không trượt.

kotlin
NavHost(
    navController = navController,
    startDestination = "home",
    enterTransition = { slideInHorizontally(initialOffsetX = { it }) + fadeIn() },
    exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) + fadeOut() },
    popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) + fadeIn() },
    popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) + fadeOut() }
) { /* composable routes */ }

Hoạt ảnh tùy chỉnh được tạo bằng Compose Animation API: AnimatedContentTransitionScope cung cấp quyền truy cập vào kích thước vùng chứa, tiến trình hoạt ảnh và hướng. Đối với chuyển tiếp phần tử dùng chung (một phần tử di chuyển mượt mà sang màn hình khác), cần thư viện Accompanist Navigation Animation hoặc triển khai tùy chỉnh qua sharedElement Modifier. Theo Android Developers (2025), hoạt ảnh trượt mặc định (vào từ bên phải, ra bên trái) được sử dụng trong 80% ứng dụng Android có điều hướng.

Câu Hỏi Thường Gặp

Có thể sử dụng nhiều NavHost trong một Activity không?

Về mặt kỹ thuật là có, nhưng không được khuyến nghị. Mỗi NavHost tạo một ngăn xếp quay lại độc lập, phá vỡ điều hướng thống nhất. Ngoại lệ là các khu vực riêng biệt, như NavHost cho nội dung chính và NavHost cho BottomSheet với điều hướng riêng của nó.

NavHost khác Scaffold trong Compose như thế nào?

NavHost là vùng chứa điều hướng chuyển đổi màn hình. Scaffold là bố cục của toàn bộ trang (TopAppBar, BottomNavigation, FloatingActionButton). Thông thường, NavHost được đặt bên trong Scaffold.content. Scaffold không quản lý điều hướng, nó chỉ cung cấp các vị trí cho thành phần UI.

Làm thế nào để chia sẻ ViewModel giữa các màn hình qua NavHost?

ViewModel được tạo trong NavBackStackEntry qua viewModel(). Để chia sẻ ViewModel giữa các màn hình, hãy sử dụng parentNavController: liên kết ViewModel dùng chung với mục cha. Một giải pháp thay thế là DI (Hilt/Koin) với phạm vi NavGraph.

Tại sao NavHost tạo lại composable mỗi lần điều hướng?

Đây là hành vi bình thường — NavHost xóa composable khỏi thành phần khi rời khỏi tuyến đường. Để bảo toàn trạng thái, hãy sử dụng rememberSaveable cho trạng thái UI và ViewModel với SavedStateHandle cho logic nghiệp vụ.

Làm thế nào để thêm xử lý 404 (tuyến đường không xác định) trong NavHost?

Thêm tuyến đường cuối cùng composable("404") và điều hướng đến nó khi nhận được deep link không xác định. NavHost không có tuyến đường catch-all — hãy kiểm tra tuyến đường trong trình xử lý intent Deep Link trước navigate(). Nếu không tìm thấy tuyến đường, hãy điều hướng đến 404.

Tổng Kết

  • NavHost — vùng chứa composable Navigation Compose liên kết NavController với đồ thị tuyến đường và hiển thị màn hình hiện tại
  • composable() đăng ký tuyến đường với đường dẫn, đối số (NavType), deep link và hoạt ảnh
  • startDestination — tuyến đường ban đầu được mở khi NavHost khởi chạy lần đầu tiên
  • Đối số được truyền qua trình giữ chỗ {param} với NavType để nhập kiểu và defaultValue cho tham số tùy chọn
  • Đồ thị lồng nhau qua navigation() cho phép nhóm các tuyến đường theo mô-đun với ngăn xếp quay lại biệt lập
  • Hoạt ảnh chuyển tiếp được cấu hình qua enterTransition/exitTransition bằng Compose Animation API
  • NavHost tự động xử lý nút quay lại, lưu trạng thái và deep link mà không cần mã bổ sung

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.

Thảo luận dự án

Đọc thêm