Jetpack Navigation Compose 2026: Điều Hướng Type-Safe và Câu Hỏi Phỏng Vấn
Tìm hiểu Jetpack Navigation Compose 2.10 với điều hướng type-safe sử dụng Kotlin Serialization. Hướng dẫn đầy đủ với ví dụ code và câu hỏi phỏng vấn Android.

Jetpack Navigation Compose 2.10 mang đến tính năng an toàn kiểu dữ liệu tại thời điểm biên dịch cho điều hướng Android thông qua Kotlin Serialization. Cách tiếp cận này loại bỏ các lỗi runtime do không khớp tham số và đơn giản hóa việc refactoring trên toàn bộ codebase lớn.
Navigation Compose 2.10.0, phát hành tháng 8 năm 2026, yêu cầu SDK tối thiểu 24. Route type-safe sử dụng data class @Serializable thay vì destination dựa trên string, giúp phát hiện lỗi điều hướng tại thời điểm biên dịch.
Thiết Lập Điều Hướng Type-Safe trong Navigation 2.10
API điều hướng type-safe yêu cầu Kotlin Serialization. Thêm các dependency sau vào file build.gradle.kts cấp module:
plugins {
id("org.jetbrains.kotlin.plugin.serialization") version "2.1.0"
}
dependencies {
implementation("androidx.navigation:navigation-compose:2.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
}Định nghĩa destination dưới dạng kiểu serializable. Sử dụng object cho màn hình không có tham số và data class cho màn hình nhận parameter:
import kotlinx.serialization.Serializable
@Serializable
object Home
@Serializable
object ProductList
@Serializable
data class ProductDetail(val productId: String)
@Serializable
data class Checkout(val cartId: String, val promoCode: String? = null)Compiler đảm bảo rằng tất cả các class route được đánh dấu với @Serializable. Thiếu annotation sẽ kích hoạt lỗi lint trong Navigation 2.9+.
Xây Dựng NavHost với Route Serializable
Composable NavHost chấp nhận trực tiếp các kiểu serializable. Mỗi block composable<T> xử lý một kiểu destination cụ thể:
@Composable
fun AppNavigation(
navController: NavHostController = rememberNavController()
) {
NavHost(
navController = navController,
startDestination = Home
) {
composable<Home> {
HomeScreen(
onNavigateToProducts = { navController.navigate(ProductList) }
)
}
composable<ProductList> {
ProductListScreen(
onProductClick = { productId ->
navController.navigate(ProductDetail(productId))
}
)
}
composable<ProductDetail> { backStackEntry ->
// Trích xuất route có kiểu từ back stack entry
val route: ProductDetail = backStackEntry.toRoute()
ProductDetailScreen(
productId = route.productId,
onCheckout = { cartId ->
navController.navigate(Checkout(cartId))
}
)
}
composable<Checkout> { backStackEntry ->
val route: Checkout = backStackEntry.toRoute()
CheckoutScreen(
cartId = route.cartId,
promoCode = route.promoCode
)
}
}
}Extension toRoute<T>() trả về một instance destination có kiểu đầy đủ. Việc đổi tên property trong data class sẽ tự động cập nhật tất cả các vị trí sử dụng thông qua tính năng refactoring của IDE.
Nested Navigation Graph cho Feature Module
Các ứng dụng lớn được hưởng lợi từ nested navigation graph. Mỗi feature module định nghĩa graph riêng, và root graph tổ hợp chúng lại:
@Serializable
object AuthGraph // Định danh graph
@Serializable
object Login
@Serializable
object Register
@Serializable
data class PasswordReset(val email: String)
fun NavGraphBuilder.authNavGraph(navController: NavHostController) {
navigation<AuthGraph>(startDestination = Login) {
composable<Login> {
LoginScreen(
onRegisterClick = { navController.navigate(Register) },
onForgotPassword = { email ->
navController.navigate(PasswordReset(email))
},
onLoginSuccess = {
navController.navigate(Home) {
popUpTo<AuthGraph> { inclusive = true }
}
}
)
}
composable<Register> {
RegisterScreen(
onRegistrationComplete = {
navController.navigate(Home) {
popUpTo<AuthGraph> { inclusive = true }
}
}
)
}
composable<PasswordReset> { backStackEntry ->
val route: PasswordReset = backStackEntry.toRoute()
PasswordResetScreen(email = route.email)
}
}
}popUpTo<AuthGraph> với inclusive = true xóa toàn bộ luồng auth khỏi back stack sau khi đăng nhập thành công, ngăn người dùng quay lại màn hình đăng nhập.
Sẵn sàng chinh phục phỏng vấn Android?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Value Class làm Tham Số Route
Navigation 2.9.0-alpha03 thêm hỗ trợ cho value class trong route. Pattern này đảm bảo an toàn kiểu cho ID và ngăn việc nhầm lẫn các parameter có cùng kiểu primitive:
@JvmInline
@Serializable
value class ProductId(val value: String)
@JvmInline
@Serializable
value class UserId(val value: String)
@Serializable
data class ProductDetail(val productId: ProductId)
@Serializable
data class UserProfile(val userId: UserId)Bây giờ compiler ngăn việc truyền UserId vào nơi yêu cầu ProductId, một lỗi phổ biến trong điều hướng dựa trên string.
Deep Link với Route Type-Safe
Deep link ánh xạ URI bên ngoài tới route serializable. Định nghĩa pattern trong block composable:
composable<ProductDetail>(
deepLinks = listOf(
navDeepLink {
uriPattern = "https://example.com/products/{productId}"
}
)
) { backStackEntry ->
val route: ProductDetail = backStackEntry.toRoute()
ProductDetailScreen(productId = route.productId)
}Placeholder {productId} được ánh xạ trực tiếp tới property productId trong data class ProductDetail. Navigation tự động trích xuất và deserialize tham số.
Đăng ký deep link trong AndroidManifest.xml:
<!-- AndroidManifest.xml -->
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="example.com"
android:pathPrefix="/products" />
</intent-filter>
</activity>Predictive Back Gesture với Navigation Compose
Android 14+ hỗ trợ predictive back gesture cho phép người dùng xem trước màn hình trước đó trước khi hoàn thành hành động. Navigation Compose 2.10 tích hợp với tính năng này thông qua SeekableTransitionState:
NavHost(
navController = navController,
startDestination = Home,
enterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Start) },
exitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start) },
popEnterTransition = { slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.End) },
popExitTransition = { slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.End) }
) {
// destination
}Predictive back gesture hiển thị destination trước đó theo thời gian thực khi người dùng vuốt. Thả gesture trước ngưỡng sẽ hủy điều hướng và quay lại màn hình hiện tại.
Câu Hỏi Phỏng Vấn Thường Gặp về Navigation Compose
Các buổi phỏng vấn Android senior thường đào sâu vào quyết định kiến trúc điều hướng. Dưới đây là những câu hỏi phân biệt ứng viên có kinh nghiệm.
Tại sao sử dụng điều hướng type-safe thay vì route dựa trên string?
Route dựa trên string như "product/{productId}" gây ra crash runtime khi tham số bị viết sai hoặc có kiểu không đúng. Route type-safe phát hiện những lỗi này tại thời điểm biên dịch. Refactoring parameter route cập nhật tất cả các vị trí sử dụng thông qua IDE tooling, trong khi route dựa trên string yêu cầu tìm và thay thế thủ công trên toàn bộ codebase.
Làm thế nào để chia sẻ dữ liệu giữa các destination mà không truyền object lớn?
Route được serialize vào saved instance state. Truyền object lớn như catalog sản phẩm có nguy cơ gây ra TransactionTooLargeException. Thay vào đó, truyền ID và load dữ liệu từ repository hoặc shared ViewModel.
Pattern để chia sẻ dữ liệu sử dụng scoped ViewModel:
@HiltViewModel
class ProductViewModel @Inject constructor(
private val repository: ProductRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
// Trích xuất route có kiểu từ SavedStateHandle
private val productId: String = savedStateHandle.toRoute<ProductDetail>().productId
val product = repository.getProduct(productId)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
}Làm thế nào để test navigation trong Compose?
Navigation testing xác minh rằng hành động của người dùng kích hoạt thay đổi destination đúng:
class NavigationTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun clickingProduct_navigatesToDetail() {
val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
navController.navigatorProvider.addNavigator(ComposeNavigator())
composeTestRule.setContent {
AppNavigation(navController = navController)
}
// Điều hướng đến danh sách sản phẩm
composeTestRule.onNodeWithText("View Products").performClick()
// Click sản phẩm đầu tiên
composeTestRule.onNodeWithTag("product_item_0").performClick()
// Xác minh destination hiện tại
val currentRoute = navController.currentBackStackEntry?.toRoute<ProductDetail>()
assertThat(currentRoute).isNotNull()
}
}Điều gì xảy ra khi NavController được sử dụng sau khi bị hủy?
Navigation 2.9.0-alpha01 thay đổi hành vi này. Sử dụng NavController sau khi Activity hoặc Fragment chứa nó bị hủy giờ đây sẽ ném IllegalStateException. Các phiên bản trước đó fail im lặng hoặc tạo ra hành vi không xác định. Thay đổi này giúp phát hiện bug lifecycle sớm hơn trong quá trình phát triển.
Làm thế nào để xử lý điều hướng có điều kiện dựa trên trạng thái auth?
Pattern phổ biến là quan sát trạng thái auth và chuyển hướng tương ứng:
@Composable
fun MainScreen(
authState: AuthState,
navController: NavHostController = rememberNavController()
) {
LaunchedEffect(authState) {
when (authState) {
is AuthState.Authenticated -> {
navController.navigate(Home) {
popUpTo(0) { inclusive = true }
}
}
is AuthState.Unauthenticated -> {
navController.navigate(AuthGraph) {
popUpTo(0) { inclusive = true }
}
}
else -> { /* loading, không làm gì */ }
}
}
NavHost(
navController = navController,
startDestination = if (authState is AuthState.Authenticated) Home else AuthGraph
) {
authNavGraph(navController)
composable<Home> { HomeScreen() }
}
}LaunchedEffect phản ứng với thay đổi trạng thái auth và xóa toàn bộ back stack với popUpTo(0) { inclusive = true } để ngăn điều hướng ngược về luồng sai.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Di Chuyển từ Route String sang Điều Hướng Type-Safe
Các dự án hiện có có thể di chuyển dần dần. Route string và route type-safe có thể cùng tồn tại trong cùng một NavHost:
NavHost(
navController = navController,
startDestination = "home" // Route string cũ
) {
// Destination dựa trên string cũ
composable("home") {
HomeScreen(onNavigateToProduct = { id ->
navController.navigate(ProductDetail(id)) // Điều hướng type-safe mới
})
}
// Destination type-safe mới
composable<ProductDetail> { backStackEntry ->
val route: ProductDetail = backStackEntry.toRoute()
ProductDetailScreen(productId = route.productId)
}
}Cách tiếp cận này cho phép di chuyển từng màn hình một mà không cần viết lại toàn bộ navigation graph.
Điểm Chính về Jetpack Navigation Compose năm 2026
- Navigation 2.10.0 là phiên bản ổn định hiện tại với SDK tối thiểu 24
- Route type-safe sử dụng data class
@Serializablethay vì destination string - Value class cung cấp an toàn kiểu bổ sung cho ID và ngăn nhầm lẫn parameter
- Nested navigation graph tổ chức feature module với back stack độc lập
- Deep link ánh xạ pattern URI trực tiếp tới property route
- Predictive back gesture tích hợp tự động với các transition tiêu chuẩn
- Tham số route nên gọn nhẹ: truyền ID, load dữ liệu từ repository
- Di chuyển từ route string có thể thực hiện dần dần với navigation graph hỗn hợp
- Chuẩn bị phỏng vấn Android đề cập chi tiết các pattern này
- Xem thêm câu hỏi phỏng vấn Jetpack Compose cho các khái niệm UI liên quan
Bạn có tìm ra lỗi trong Android không?
Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Viết bởi
Anthony Fillion-MailletNgười sáng lập SharpSkill
Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.
Cập nhật ngày 1 tháng 9, 2026
Chia sẻ
Bài viết liên quan

Modularization Android 2026: Kiến Trúc Multi-Module và Câu Hỏi Phỏng Vấn
Làm chủ kiến trúc multi-module Android với convention plugins, Gradle version catalogs và feature modules. Bao gồm các câu hỏi phỏng vấn phổ biến về chiến lược modularization.

Android CameraX 2026: Hướng Dẫn Chụp Ảnh và Quay Video với Câu Hỏi Phỏng Vấn
Tìm hiểu CameraX trên Android 2026: chụp ảnh, quay video, tích hợp Jetpack Compose và các câu hỏi phỏng vấn kỹ thuật dành cho lập trình viên Android.

Android WorkManager 2026: Hướng Dẫn Toàn Diện Background Tasks, Constraints và Câu Hỏi Phỏng Vấn
Tìm hiểu Android WorkManager để chạy background tasks với constraints, chaining và CoroutineWorker. Bao gồm các câu hỏi phỏng vấn Android developer thường gặp.