composable() เป็นฟังก์ชันของไลบรารี Navigation Compose ที่ลงทะเบียนหน้าจอใน NavHost และเชื่อมต่อเส้นทาง URL กับเลย์เอาต์ Compose เมื่อการนำทางไปยังเส้นทางที่กำหนด Jetpack Compose จะเรียกฟังก์ชัน composable ที่เกี่ยวข้องและแสดงเป็นหน้าจอปัจจุบัน ซึ่งแตกต่างจาก FragmentManager หรือการนำทางแบบ Intent-based composable() ทำงานในระดับ Activity เดียวและถูกจัดการทั้งหมดผ่าน Kotlin DSL ตามข้อมูลจาก Android Developers (2025) แอปพลิเคชัน Android สมัยใหม่ที่สร้างด้วย Jetpack Compose กว่า 73% ใช้ Navigation Compose เพื่อจัดการการเปลี่ยนหน้าจอ
ประเด็นสำคัญ
composable() เป็นฟังก์ชันส่วนขยายของออบเจกต์ NavHost Kotlin DSL อนุญาตให้เรียกใช้ภายในบล็อก NavHost เพื่ออธิบายหน้าจอทั้งหมดของแอปพลิเคชันแบบประกาศ แต่ละการเรียกสร้างรายการในกราฟการนำทาง เชื่อมต่อเส้นทางสตริงกับฟังก์ชัน composable เมื่อผู้ใช้ไปยังเส้นทางที่ระบุ NavHost จะแสดง composable ที่เกี่ยวข้องเป็นหน้าจอปัจจุบัน โดยซ่อนหน้าจอก่อนหน้า
ไลบรารี Navigation Compose ถูกนำเสนอโดย Google ในปี 2021 เพื่อเป็นทางเลือกแทนการนำทางแบบ Fragment-based สำหรับ Jetpack Compose ข้อได้เปรียบหลักคือความเข้ากันได้อย่างสมบูรณ์กับกระบวนทัศน์ Compose: composable() ทำงานในวงจรชีวิตเดียวกันกับส่วนประกอบ Compose อื่น ๆ โดยไม่ต้องใช้ FragmentManager หรือธุรกรรม ซึ่งกำจัดคลาสของข้อบกพร่องที่เกี่ยวข้องกับความไม่สอดคล้องของวงจรชีวิตระหว่าง Fragment และ Compose
แต่ละ composable() รับเส้นทางสตริงและฟังก์ชันแลมบ์ดาที่รับออบเจกต์ NavBackStackEntry และส่งคืน UI ที่ประกอบได้ ภายในแลมบ์ดา สามารถเข้าถึง NavController ผ่าน navController จากขอบเขต ทำให้สามารถนำทางไปยังหน้าจออื่น ๆ สถาปัตยกรรมนี้ทำให้การนำทางชัดเจนและคาดเดาได้
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProfile = {
navController.navigate("profile")
}
)
}
composable("profile") {
ProfileScreen(
onBack = { navController.popBackStack() }
)
}
}
}
แต่ละการเรียก composable() สร้างจุดยอดด้วยตัวระบุเส้นทางที่ไม่ซ้ำกันในกราฟภายในของ NavHost เมื่อ NavController ดำเนินการ navigate() ไลบรารีจะเปรียบเทียบเส้นทางที่ร้องกับจุดยอด composable ที่ลงทะเบียนทั้งหมดและค้นหาคู่ที่ตรงกัน หลังจากตรงกัน จะมีการสร้าง NavBackStackEntry วางบนสแต็กการนำทาง และเริ่มการประกอบ UI
การทำงานภายในของ composable() ใช้กลไก การเริ่มต้นแบบขี้เกียจ: การประกอบหน้าจอจะเกิดขึ้นเฉพาะในการนำทางครั้งแรกไปยังเส้นทางนั้น ซึ่งหมายความว่าหน้าจอที่ผู้ใช้ไม่เคยไปไม่ใช้หน่วยความจำและไม่ดำเนินการใด ๆ วิธีการนี้ช่วยปรับปรุงประสิทธิภาพอย่างมีนัยสำคัญในแอปพลิเคชันที่มีหลายหน้าจอ
พารามิเตอร์ key ใน composable() ช่วยให้จัดการการสร้างหน้าจอใหม่ได้ โดยค่าเริ่มต้น composable จะไม่ถูกสร้างใหม่เมื่อนำทางซ้ำไปยังเส้นทางเดิม — NavHost ใช้รายการสแต็กย้อนกลับที่มีอยู่ อย่างไรก็ตาม หากมีการส่ง key และมีการเปลี่ยนแปลง NavHost จะสร้างอินสแตนซ์ใหม่ของฟังก์ชัน composable ซึ่งมีประโยชน์สำหรับหน้าจอที่มีข้อมูลไดนามิกซึ่งจำเป็นต้องบังคับรีเฟรชสถานะเมื่อเปิดอีกครั้ง
val NavGraphBuilder.Composable: Unit
get() = composable(
route = "details/{itemId}",
arguments = listOf(
NavArgument("itemId") {
type = NavType.IntType
}
),
deepLinks = listOf(
navDeepLink { uriPattern = "myapp://details/{itemId}" }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailsScreen(itemId = itemId)
}
composable() รองรับระบบอาร์กิวเมนต์ที่ยืดหยุ่นผ่านพารามิเตอร์ arguments แต่ละอาร์กิวเมนต์ถูกอธิบายโดยออบเจกต์ NavArgument ที่กำหนดประเภท ค่าเริ่มต้น และความจำเป็น อาร์กิวเมนต์ถูกส่งในเส้นทางเป็นพารามิเตอร์เส้นทาง (ผ่านเครื่องหมายปีกกา) หรือพารามิเตอร์คำค้นหา (ผ่านเครื่องหมายคำถาม)
พารามิเตอร์เส้นทางถูกระบุโดยตรงในเทมเพลตเส้นทาง: "profile/{userId}" เมื่อนำทางไปยัง "profile/42" NavHost จะแยกค่า 42 โดยอัตโนมัติและทำให้เข้าถึงได้ผ่าน backStackEntry.arguments พารามิเตอร์คำค้นหาจะถูกเพิ่มหลังเครื่องหมายคำถาม: "search?query={text}" และถูกแยกวิเคราะห์โดยอัตโนมัติโดยไลบรารี
เมื่อแยกอาร์กิวเมนต์ สิ่งสำคัญคือต้องตรวจสอบความจำเป็นของพารามิเตอร์ผ่าน NavType.isNullableAllowed และให้ค่าเริ่มต้นผ่าน NavArgument defaultValue หากพารามิเตอร์ที่จำเป็นหายไป Navigation Compose จะโยน IllegalArgumentException เพื่อป้องกันข้อบกพร่องเล็กน้อยกับเส้นทางที่ไม่ถูกต้อง
| ประเภทอาร์กิวเมนต์ | NavType | ตัวอย่างในเส้นทาง |
|---|---|---|
| Int | NavType.IntType | "item/{id}" |
| String | NavType.StringType | "user/{name}" |
| Boolean | NavType.BoolType | "filter?enabled={value}" |
| Float | NavType.FloatType | "map/{lat}/{lon}" |
| Long | NavType.LongType | "article/{timestamp}" |
สำหรับการส่งออบเจกต์ที่ซับซ้อน แนะนำให้ใช้ NavType.ParcelableType หรือ NavType.SerializableType อย่างไรก็ตาม Google แนะนำให้ลดขนาดของข้อมูลที่ถ่ายโอน — ควรส่งตัวระบุและโหลดออบเจกต์ตาม ID ภายในหน้าจอ ซึ่งป้องกันปัญหากับข้อมูลซีเรียลไลซ์ขนาดใหญ่และทำให้การจัดการการเปลี่ยนแปลงการกำหนดค่าง่ายขึ้น
data class Profile(val id: Int, val name: String) : Parcelable
// นำทางด้วยข้อมูลน้อยที่สุด
navController.navigate("profile/42")
// ดึงอาร์กิวเมนต์บนหน้าจอ
composable(
route = "profile/{userId}",
arguments = listOf(
NavArgument("userId") { type = NavType.IntType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
ProfileDetailScreen(userId = userId)
}
ในแอปพลิเคชันจริง มักจำเป็นต้องจัดระเบียบกราฟการนำทางแบบซ้อน — ตัวอย่างเช่น สแต็กหน้าจอแยกต่างหากภายในแท็บ BottomNavigation composable() รองรับการซ้อนผ่าน NavHost ที่ซ้อนกัน: ภายในหน้าจอ composable คุณสามารถประกาศ NavHost ของคุณเองที่มีสแต็กเส้นทางอิสระ
แต่ละ NavHost ที่ซ้อนกันมี NavController และสแต็กย้อนกลับของตัวเอง ซึ่งหมายความว่าการนำทางภายในแท็บหนึ่งไม่ส่งผลต่อการนำทางในแท็บอื่น ๆ — ผู้ใช้สามารถสลับระหว่างแท็บได้อย่างอิสระโดยไม่สูญเสียประวัติการนำทางภายในแต่ละแท็บ สถาปัตยกรรมนี้เรียกว่า Scoped Navigation และ Google แนะนำสำหรับแอปพลิเคชันที่มีการนำทางหลายระดับที่ซับซ้อน
เมื่อ implement การนำทางแบบซ้อน สิ่งสำคัญคือต้องจัดการสถานะ NavController อย่างถูกต้อง: แต่ละ NavHost ที่ซ้อนกันควรเก็บ rememberNavController ของตัวเองภายในขอบเขตของฟังก์ชัน composable ตามข้อมูลจาก Android Developer Summit 2024 แอปพลิเคชัน Jetpack Compose ที่มีสามแท็บขึ้นไปกว่า 40% ใช้สถาปัตยกรรม NavHost ที่ซ้อนกันเพื่อแยกการนำทางระหว่างโมดูล
// NavHost หลักพร้อมแท็บ
composable("tabs") {
MainTabsScreen { tab ->
when (tab) {
Tab.Home -> HomeNavGraph()
Tab.Search -> SearchNavGraph()
}
}
}
// กราฟแบบซ้อนภายในแท็บหน้าแรก
@Composable
fun HomeNavGraph() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home_feed"
) {
composable("home_feed") { FeedScreen() }
composable("home_detail/{postId}") { PostDetailScreen() }
}
}
ก่อน Jetpack Compose วิธีการนำทางมาตรฐานใน Android ใช้ Intent และ FragmentManager Intent เป็นข้อความของระบบที่เริ่ม Activity ใหม่ ซึ่งหมายถึงการสร้างต้นไม้ View ทั้งหมดใหม่ ในทางตรงกันข้าม composable() ทำงานภายใน Activity เดียวและเพียงแค่แทนที่ส่วนหนึ่งของต้นไม้ Compose ซึ่งเร็วกว่าอย่างมีนัยสำคัญและมีประสิทธิภาพด้านหน่วยความจำมากกว่า
ความแตกต่างหลักระหว่าง composable() และการนำทางแบบ Intent-based:
| คุณลักษณะ | composable() | Intent / Fragment |
|---|---|---|
| สถาปัตยกรรม | Activity เดียว, ต้นไม้ Compose | หลาย Activity, สแต็ก Fragment |
| การถ่ายโอนข้อมูล | พารามิเตอร์ path/query, ViewModel ที่ใช้ร่วมกัน | Intent extras, Bundle, SharedPreferences |
| ลิงก์ลึก | รองรับ navDeepLink ในตัว | intent-filter ใน manifest |
| สแต็กย้อนกลับ | การจัดการ popBackStack อัตโนมัติ | FragmentManager.popBackStack() |
| เวลาในการสลับ | 5–15 มิลลิวินาที (ภายในกระบวนการ) | 50–200 มิลลิวินาที (พร้อมการสร้างใหม่) |
การเปลี่ยนจาก Intent เป็น composable() ไม่ใช่แค่การแทนที่ API แต่เป็นการเปลี่ยนแปลงกระบวนทัศน์ทางสถาปัตยกรรม แทนที่จะระบุอย่างชัดเจนว่า Activity ใดควรเปิด นักพัฒนาอธิบายเส้นทางที่เป็นไปได้ทั้งหมดแบบประกาศในที่เดียว ซึ่งช่วยเพิ่มความสามารถในการอ่านโค้ดและทำให้การทดสอบการนำทางง่ายขึ้น ตามข้อมูลจาก Google I/O 2024 Jetpack Compose กับ Navigation Compose ลดโค้ดการนำทางลง 40–60% เมื่อเทียบกับ FragmentManager
หนึ่งในข้อผิดพลาดที่พบบ่อยที่สุดคือ การสร้าง NavController ใหม่ ระหว่างการประกอบซ้ำ หาก NavController ถูกสร้างผ่าน rememberNavController() ที่ระดับ composable หลัก ซึ่งอาจถูกสร้างใหม่เมื่อสถานะเปลี่ยนแปลง การนำทางจะพัง — ประวัติจะสูญหาย วิธีแก้ไขที่ถูกต้องคือยก NavController ไปยังระดับ composable ที่เสถียร เช่น ระดับ Activity หรือ composable หลักของแอปพลิเคชัน
ปัญหาที่พบบ่อยอันดับสองคือ การประกอบซ้ำไม่รู้จบ ระหว่างการนำทาง สิ่งนี้เกิดขึ้นเมื่อ navController.navigate() ถูกวางโดยตรงในเนื้อหาของฟังก์ชัน composable เนื่องจากการนำทางเปลี่ยนสถานะของ NavHost จึงกระตุ้นให้เกิดการประกอบซ้ำ ซึ่งเรียก navigate() อีกครั้ง ทำให้เกิดลูป การเรียกนำทางทั้งหมดควรถูกห่อในแลมบ์ดาตัวจัดการ (onClick, onButtonPressed) ไม่ควรดำเนินการในการประกอบ
ข้อผิดพลาดที่สามคือ การจัดการสแต็กย้อนกลับที่ไม่ถูกต้อง เมื่อใช้ BottomNavigation การนำทางแบบง่ายผ่าน navigate() ในแต่ละการสลับแท็บจะเพิ่มรายการใหม่ลงในสแต็กแทนที่จะกลับไปยังรายการที่มีอยู่ สำหรับ BottomNavigation คุณควรใช้ navController.navigate() ด้วย restoreState = true และ launchSingleTop = true ซึ่งรับประกันการกู้คืนสถานะที่ถูกต้องเมื่อสลับแท็บ
fun NavController.navigateToTab(route: String) {
navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
คำถามที่พบบ่อย
composable() ไม่ใช่คำอธิบายประกอบ แต่เป็นฟังก์ชันส่วนขยายของ NavHost ที่เชื่อมโยงเส้นทางกับ UI ฟังก์ชัน @Composable ปกติเพียงอธิบายเลย์เอาต์ ในขณะที่ composable() ลงทะเบียนเลย์เอาต์นั้นในกราฟการนำทางด้วยเส้นทางที่ระบุ ทำให้สามารถนำทางผ่าน NavController ได้
แนะนำให้ส่งเฉพาะ ตัวระบุ (ID) ผ่านพารามิเตอร์เส้นทาง และโหลดออบเจกต์บนหน้าจอตาม ID ผ่าน repository หรือ ViewModel หากยังจำเป็นต้องส่งออบเจกต์ ให้ใช้ NavType.ParcelableType แต่หลีกเลี่ยงการส่งออบเจกต์ที่มีขนาดใหญ่กว่า 1 KB — อาจทำให้เกิด TransactionTooLargeException
การหมุนหน้าจอทำให้เกิด การเปลี่ยนการกำหนดค่า ซึ่งโดยค่าเริ่มต้นจะสร้าง Activity ใหม่ เพื่อรักษาสถานะของหน้าจอ composable ให้ใช้ rememberSaveable สำหรับข้อมูลง่าย ๆ หรือ ViewModel ที่มีขอบเขตของหน้าจอนั้น Navigation Compose กู้คืนสแต็กย้อนกลับหลังการสร้างใหม่ แต่สถานะภายในฟังก์ชัน composable() จะถูกรีเซ็ตโดยไม่มี rememberSaveable
ไม่ composable() เป็นฟังก์ชันส่วนขยายของ NavGraphBuilder ซึ่งใช้ได้เฉพาะภายในบล็อก NavHost เท่านั้น สำหรับการแทนที่ UI อย่างง่ายโดยไม่ต้องนำทาง ให้ใช้การแสดงผลแบบมีเงื่อนไข (when, if) หรือ AnimatedContent composable() ออกแบบมาเฉพาะสำหรับการกำหนดเส้นทางพร้อมรองรับสแต็กย้อนกลับและลิงก์ลึก
ใช้ SavedStateHandle ภายใน ViewModel: ในการนำทางครั้งแรก handle.get("initialized") จะคืนค่า null ในการนำทางกลับ จะคืนค่าที่บันทึกไว้ หรือวิเคราะห์ตำแหน่งปัจจุบันในสแต็กย้อนกลับผ่าน navController.previousBackStackEntry — หากเป็น null แสดงว่าเป็นหน้าจอแรกในสแต็กการนำทาง
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม