# Jetpack Navigation Compose 2026: การนำทางแบบ Type-Safe และคำถามสัมภาษณ์งาน > เรียนรู้ Jetpack Navigation Compose 2.10 ที่มีการนำทางแบบ type-safe ด้วย Kotlin Serialization คู่มือฉบับสมบูรณ์พร้อมตัวอย่างโค้ดและคำถามสัมภาษณ์ Android - Published: 2026-09-01 - Updated: 2026-09-01 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- Jetpack Navigation Compose 2.10 นำเสนอความปลอดภัยของชนิดข้อมูลในขณะคอมไพล์สำหรับการนำทาง Android ผ่าน Kotlin Serialization แนวทางนี้ขจัดข้อผิดพลาด runtime จากการไม่ตรงกันของ argument และทำให้การ refactoring ง่ายขึ้นในโค้ดเบสขนาดใหญ่ > **Navigation 2.10 เวอร์ชันเสถียร** > > Navigation Compose 2.10.0 ที่เปิดตัวในเดือนสิงหาคม 2026 ต้องการ SDK ขั้นต่ำ 24 Route แบบ type-safe ใช้ data class ที่มี `@Serializable` แทนที่ destination ที่เป็น string ทำให้ตรวจจับข้อผิดพลาดการนำทางได้ในขณะคอมไพล์ ## การตั้งค่าการนำทางแบบ Type-Safe ใน Navigation 2.10 API การนำทางแบบ type-safe ต้องการ Kotlin Serialization เพิ่ม dependency เหล่านี้ลงใน `build.gradle.kts` ระดับ module: ```kotlin // build.gradle.kts 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") } ``` กำหนด destination เป็นชนิด serializable ใช้ `object` สำหรับหน้าจอที่ไม่มี argument และ `data class` สำหรับหน้าจอที่รับ parameter: ```kotlin // Routes.kt 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) ``` คอมไพเลอร์รับรองว่า class route ทั้งหมดถูกทำเครื่องหมายด้วย `@Serializable` การขาด annotation จะทำให้เกิดข้อผิดพลาด lint ใน Navigation 2.9+ ## การสร้าง NavHost ด้วย Route แบบ Serializable Composable `NavHost` รับชนิด serializable โดยตรง แต่ละบล็อก `composable` จัดการชนิด destination เฉพาะ: ```kotlin // AppNavigation.kt @Composable fun AppNavigation( navController: NavHostController = rememberNavController() ) { NavHost( navController = navController, startDestination = Home ) { composable { HomeScreen( onNavigateToProducts = { navController.navigate(ProductList) } ) } composable { ProductListScreen( onProductClick = { productId -> navController.navigate(ProductDetail(productId)) } ) } composable { backStackEntry -> // ดึง route ที่มีชนิดจาก back stack entry val route: ProductDetail = backStackEntry.toRoute() ProductDetailScreen( productId = route.productId, onCheckout = { cartId -> navController.navigate(Checkout(cartId)) } ) } composable { backStackEntry -> val route: Checkout = backStackEntry.toRoute() CheckoutScreen( cartId = route.cartId, promoCode = route.promoCode ) } } } ``` Extension `toRoute()` คืนค่า instance ของ destination ที่มีชนิดครบถ้วน การเปลี่ยนชื่อ property ใน data class จะอัปเดตการใช้งานทั้งหมดโดยอัตโนมัติผ่านการ refactor ของ IDE ## Nested Navigation Graph สำหรับ Feature Module แอปพลิเคชันขนาดใหญ่ได้รับประโยชน์จาก nested navigation graph แต่ละ feature module กำหนด graph ของตัวเอง และ root graph รวมพวกมันเข้าด้วยกัน: ```kotlin // AuthNavigation.kt @Serializable object AuthGraph // ตัวระบุ graph @Serializable object Login @Serializable object Register @Serializable data class PasswordReset(val email: String) fun NavGraphBuilder.authNavGraph(navController: NavHostController) { navigation(startDestination = Login) { composable { LoginScreen( onRegisterClick = { navController.navigate(Register) }, onForgotPassword = { email -> navController.navigate(PasswordReset(email)) }, onLoginSuccess = { navController.navigate(Home) { popUpTo { inclusive = true } } } ) } composable { RegisterScreen( onRegistrationComplete = { navController.navigate(Home) { popUpTo { inclusive = true } } } ) } composable { backStackEntry -> val route: PasswordReset = backStackEntry.toRoute() PasswordResetScreen(email = route.email) } } } ``` `popUpTo` ที่มี `inclusive = true` จะลบ flow การยืนยันตัวตนทั้งหมดออกจาก back stack หลังจากเข้าสู่ระบบสำเร็จ ป้องกันไม่ให้ผู้ใช้นำทางกลับไปที่หน้าจอเข้าสู่ระบบ ## Value Class เป็น Argument ของ Route Navigation 2.9.0-alpha03 เพิ่มการสนับสนุน [value class](https://kotlinlang.org/docs/inline-classes.html) ใน route รูปแบบนี้บังคับความปลอดภัยของชนิดสำหรับ ID และป้องกันการสลับ parameter ที่มีชนิด primitive เดียวกัน: ```kotlin // Domain.kt @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) ``` ตอนนี้คอมไพเลอร์ป้องกันการส่ง `UserId` ไปยังที่ที่คาดหวัง `ProductId` ซึ่งเป็นบั๊กที่พบบ่อยในการนำทางที่ใช้ string ## Deep Link กับ Route แบบ Type-Safe Deep link ทำการ map URI ภายนอกไปยัง route ที่ serializable กำหนด pattern ในบล็อก `composable`: ```kotlin // AppNavigation.kt composable( deepLinks = listOf( navDeepLink { uriPattern = "https://example.com/products/{productId}" } ) ) { backStackEntry -> val route: ProductDetail = backStackEntry.toRoute() ProductDetailScreen(productId = route.productId) } ``` Placeholder `{productId}` ถูก map โดยตรงกับ property `productId` ใน data class `ProductDetail` Navigation จะดึงและ deserialize argument โดยอัตโนมัติ ลงทะเบียน deep link ใน `AndroidManifest.xml`: ```xml ``` ## Predictive Back Gesture กับ Navigation Compose Android 14+ รองรับ predictive back gesture ที่ผู้ใช้เห็นตัวอย่างหน้าจอก่อนหน้าก่อนที่จะดำเนินการ Navigation Compose 2.10 รวมเข้ากับฟีเจอร์นี้ผ่าน `SeekableTransitionState`: ```kotlin // AppNavigation.kt 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 แสดง destination ก่อนหน้าแบบ real-time ขณะที่ผู้ใช้ปัด การปล่อย gesture ก่อนถึง threshold จะยกเลิกการนำทางและกลับไปที่หน้าจอปัจจุบัน ## คำถามสัมภาษณ์งานที่พบบ่อยเกี่ยวกับ Navigation Compose การสัมภาษณ์ Android ระดับ senior มักจะเจาะลึกเรื่องการตัดสินใจด้านสถาปัตยกรรมการนำทาง ต่อไปนี้คือคำถามที่แยกแยะผู้สมัครที่มีประสบการณ์ ### ทำไมต้องใช้การนำทางแบบ type-safe แทน route ที่เป็น string? Route ที่เป็น string เช่น `"product/{productId}"` ทำให้เกิด crash runtime เมื่อ argument ถูกสะกดผิดหรือมีชนิดไม่ถูกต้อง Route แบบ type-safe ตรวจจับข้อผิดพลาดเหล่านี้ในขณะคอมไพล์ การ refactor parameter ของ route จะอัปเดตการใช้งานทั้งหมดผ่าน IDE tooling ในขณะที่ route ที่เป็น string ต้องการการค้นหาและแทนที่ด้วยตนเองทั่วทั้งโค้ดเบส ### จะแชร์ข้อมูลระหว่าง destination โดยไม่ส่ง object ขนาดใหญ่ได้อย่างไร? > **หลีกเลี่ยง Argument ของ Route ที่มีขนาดใหญ่** > > Route ถูก serialize ลงใน saved instance state การส่ง object ขนาดใหญ่เช่นแคตตาล็อกสินค้าเสี่ยงต่อ `TransactionTooLargeException` ให้ส่ง ID แล้วโหลดข้อมูลจาก repository หรือ shared ViewModel แทน รูปแบบสำหรับการแชร์ข้อมูลใช้ scoped ViewModel: ```kotlin // ProductViewModel.kt @HiltViewModel class ProductViewModel @Inject constructor( private val repository: ProductRepository, savedStateHandle: SavedStateHandle ) : ViewModel() { // ดึง route ที่มีชนิดจาก SavedStateHandle private val productId: String = savedStateHandle.toRoute().productId val product = repository.getProduct(productId) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null) } ``` ### จะทดสอบ navigation ใน Compose ได้อย่างไร? การทดสอบ navigation ตรวจสอบว่าการกระทำของผู้ใช้ทำให้เกิดการเปลี่ยนแปลง destination ที่ถูกต้อง: ```kotlin // NavigationTest.kt 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) } // นำทางไปยังรายการสินค้า composeTestRule.onNodeWithText("View Products").performClick() // คลิกสินค้าแรก composeTestRule.onNodeWithTag("product_item_0").performClick() // ตรวจสอบ destination ปัจจุบัน val currentRoute = navController.currentBackStackEntry?.toRoute() assertThat(currentRoute).isNotNull() } } ``` ### เกิดอะไรขึ้นเมื่อ NavController ถูกใช้หลังจากถูกทำลาย? Navigation 2.9.0-alpha01 เปลี่ยนพฤติกรรมนี้ การใช้ `NavController` หลังจาก `Activity` หรือ `Fragment` ที่โฮสต์มันถูกทำลาย ตอนนี้จะโยน `IllegalStateException` เวอร์ชันก่อนหน้าล้มเหลวอย่างเงียบๆ หรือสร้างพฤติกรรมที่ไม่แน่นอน การเปลี่ยนแปลงนี้ทำให้พบบั๊ก lifecycle เร็วขึ้นในการพัฒนา ### จะจัดการการนำทางแบบมีเงื่อนไขตามสถานะ auth ได้อย่างไร? รูปแบบทั่วไปคือสังเกตสถานะ auth และเปลี่ยนเส้นทางตามนั้น: ```kotlin // MainScreen.kt @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 -> { /* กำลังโหลด ไม่ทำอะไร */ } } } NavHost( navController = navController, startDestination = if (authState is AuthState.Authenticated) Home else AuthGraph ) { authNavGraph(navController) composable { HomeScreen() } } } ``` `LaunchedEffect` ตอบสนองต่อการเปลี่ยนแปลงสถานะ auth และล้าง back stack ทั้งหมดด้วย `popUpTo(0) { inclusive = true }` เพื่อป้องกันการนำทางกลับไปยัง flow ที่ผิด ## การย้ายจาก Route แบบ String ไปยังการนำทางแบบ Type-Safe โปรเจกต์ที่มีอยู่สามารถย้ายได้ทีละขั้นตอน Route แบบ string และ route แบบ type-safe สามารถอยู่ร่วมกันใน `NavHost` เดียวกัน: ```kotlin // AppNavigation.kt NavHost( navController = navController, startDestination = "home" // Route แบบ string เก่า ) { // Destination แบบ string เก่า composable("home") { HomeScreen(onNavigateToProduct = { id -> navController.navigate(ProductDetail(id)) // การนำทางแบบ type-safe ใหม่ }) } // Destination แบบ type-safe ใหม่ composable { backStackEntry -> val route: ProductDetail = backStackEntry.toRoute() ProductDetailScreen(productId = route.productId) } } ``` วิธีการนี้อนุญาตให้ย้ายทีละหน้าจอโดยไม่ต้องเขียน navigation graph ใหม่ทั้งหมด ## ประเด็นสำคัญสำหรับ Jetpack Navigation Compose ในปี 2026 - Navigation 2.10.0 เป็นเวอร์ชันเสถียรปัจจุบันที่มี SDK ขั้นต่ำ 24 - Route แบบ type-safe ใช้ data class ที่มี `@Serializable` แทน destination แบบ string - Value class ให้ความปลอดภัยของชนิดเพิ่มเติมสำหรับ ID และป้องกันการสลับ parameter - Nested navigation graph จัดระเบียบ feature module ด้วย back stack อิสระ - Deep link ทำการ map รูปแบบ URI โดยตรงกับ property ของ route - Predictive back gesture รวมเข้ากันโดยอัตโนมัติกับ transition มาตรฐาน - Argument ของ route ควรเบา: ส่ง ID แล้วโหลดข้อมูลจาก repository - การย้ายจาก route แบบ string สามารถทำได้ทีละขั้นด้วย navigation graph แบบผสม - [การเตรียมตัวสัมภาษณ์ Android](/technologies/android) ครอบคลุมรูปแบบเหล่านี้อย่างละเอียด - ดูเพิ่มเติมที่ [คำถามสัมภาษณ์ Jetpack Compose](/blog/android/jetpack-compose-interview-questions) สำหรับแนวคิด UI ที่เกี่ยวข้อง --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/android/jetpack-navigation-compose-type-safe-2026