NavController เป็นส่วนประกอบหลักของไลบรารี Navigation Compose ที่จัดการสแต็กการนำทางและสถานะ back stack ในแอปพลิเคชัน Android ผ่าน NavController จะดำเนินการเปลี่ยนระหว่างหน้าจอ การกลับไปยังหน้าก่อนหน้า และการถ่ายโอนข้อมูลระหว่างเส้นทาง ตามข้อมูลจาก Android Developers (2025) NavController เป็นองค์ประกอบบังคับของแอปพลิเคชัน Compose ใดๆ ที่มีมากกว่าหนึ่งหน้าจอ ตัวควบคุมถูกสร้างขึ้นผ่าน rememberNavController() ส่งต่อไปยัง NavHost และพร้อมให้เรียก navigate() จากจุดใดๆ ในคอมโพซิชัน การสนับสนุน SavedStateHandle ในตัวจะบันทึกสถานะ ViewModel โดยอัตโนมัติระหว่างการกำหนดค่าใหม่
ประเด็นสำคัญ
NavController เป็นคลาสจากไลบรารี Navigation Compose ที่ใช้งานตัวควบคุมการนำทางสำหรับแอปพลิเคชัน Compose NavController จัดการสแต็ก NavBackStackEntry ซึ่งแต่ละรายการประกอบด้วยเส้นทาง อาร์กิวเมนต์ และสถานะหน้าจอ ตัวควบคุมรองรับการดำเนินการนำทางพื้นฐาน: การเปลี่ยน การกลับมา การแทนที่ และการล้าง
แตกต่างจากระบบ View ที่การนำทางผ่าน FragmentManager หรือ Intent NavController ทำงานเฉพาะในบริบทของ Compose เท่านั้น Back stack ถูกจัดเก็บเป็นกราฟ NavDestination แทนที่จะเป็นสแต็ก Fragment ซึ่งช่วยลดโอเวอร์เฮดในการสร้างและทำลาย Fragment และทำให้การทดสอบง่ายขึ้น — NavController สามารถจำลองผ่าน TestNavHostController
NavController เชื่อมโยงอย่างใกล้ชิดกับ NavHost — คอนเทนเนอร์ที่เรนเดอร์หน้าจอปัจจุบันจากกราฟ หากไม่มี NavHost NavController จะไม่สามารถแสดงฟังก์ชัน composable ได้ แต่ยังคงความสามารถในการจัดการสแต็ก ในสถาปัตยกรรมทั่วไป NavController ถูกสร้างขึ้นที่ระดับ Activity หรือ composable หลักและส่งลงไปในทรีคอมโพซิชันผ่านพารามิเตอร์
ตามข้อมูลของ Google NavController ผ่านการเผยแพร่หลักหลายครั้ง เวอร์ชัน 2.8.0 เพิ่ม Type-Safe Navigation เวอร์ชัน 2.9.0 เพิ่มการสนับสนุน predictive back gesture (Android 14+) ตัวควบคุมเข้ากันได้กับ Material3 Scaffold และ BottomNavigation สำหรับโปรเจกต์แบบหลายโมดูล NavController จะถูกส่งผ่าน DI (Hilt/Koin) หรือพารามิเตอร์คอนสตรักเตอร์
NavController ถูกสร้างขึ้นผ่านฟังก์ชัน composable rememberNavController() ฟังก์ชันส่งคืนอินสแตนซ์ของ NavHostController (คลาสย่อยของ NavController) ที่ผูกกับวงจรชีวิตของ composable ปัจจุบัน เมื่อออกจากคอมโพซิชัน ตัวควบคุมจะถูกล้าง เพื่อรักษาตัวควบคุมระหว่างการกำหนดค่าใหม่ ให้ใช้ rememberSaveable หรือ ViewModel
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
composable("details") { DetailsScreen(navController) }
}
}
การกำหนดค่า NavController ประกอบด้วย: NavHostController (หลัก), TestNavHostController (การทดสอบ) และ ScopedNavController (ลูกสำหรับกราฟที่ซ้อนกัน) สำหรับ BottomNavigation NavController ควรเป็นเพียงตัวเดียวสำหรับทั้งแอปพลิเคชัน — การสร้างตัวควบคุมใหม่ในแต่ละแท็บจะทำให้สแต็กสูญหาย ในการส่งตัวควบคุมไปยังหน้าจอที่ซ้อนกัน ให้ใช้พารามิเตอร์ฟังก์ชันแทน CompositionLocalProvider เพื่อรักษาความสามารถในการอ่าน
สำหรับการทดสอบการนำทาง ให้ใช้ TestNavHostController กับ compose-test-rule ตัวควบคุมช่วยให้ตั้งค่าเส้นทางเริ่มต้นและตรวจสอบว่า navigate() ทริกเกอร์การเปลี่ยนที่คาดหวัง การทดสอบ NavController ไม่ต้องใช้โปรแกรมจำลอง — ทำงานร่วมกับ Semantic matcher ของ Compose Test
เมธอด navigate(route: String) เป็นกลไกการนำทางหลักใน NavController รับสตริงเส้นทาง NavOptions ที่เลือกได้ และ Navigator.Extras NavOptions ควบคุมพฤติกรรมการเปลี่ยน: launchSingleTop (ไม่ทำซ้ำเส้นทางในสแต็ก), popUpTo (ล้างสแต็กจนถึงเส้นทาง), restoreState (คืนค่าสถานะก่อนหน้า)
NavOptions ถูกตั้งค่าผ่านไวยากรณ์ builder: NavOptionsBuilder พารามิเตอร์หลัก: popUpTo (เส้นทาง + inclusive/saveState), launchSingleTop (Boolean, true — ไม่สร้างสำเนา), restoreState (คืนค่าสถานะเมื่อกลับมา) หากไม่มี popUpTo แต่ละ navigate() จะเพิ่มรายการลงในสแต็ก ทำให้เกิดการสะสมของ back stack และพฤติกรรมที่ไม่ถูกต้องของปุ่ม Back
navController.navigate("profile/42") {
popUpTo("main") { saveState = true }
launchSingleTop = true
restoreState = true
}
Navigator.Extras อนุญาตให้ส่งข้อมูลเพิ่มเติมที่ไม่ได้เป็นส่วนหนึ่งของเส้นทาง: องค์ประกอบที่ใช้ร่วมกันสำหรับแอนิเมชัน, แฟล็ก Intent, ชุด Pac-Man Extras ถูกใช้น้อยมาก — ส่วนใหญ่สำหรับการทำงานร่วมกับ Accompanist Animation หรือ Navigator ที่กำหนดเอง สำหรับสถานการณ์ส่วนใหญ่ สตริงเส้นทางและ NavOptions ก็เพียงพอ
popBackStack() เป็นวิธีการกลับไปยังหน้าจอก่อนหน้า โดยไม่มีอาร์กิวเมนต์ จะลบรายการบนสุดของสแต็กและคืนค่า true หากการลบสำเร็จ หากสแต็กว่างเปล่า เมธอดจะคืนค่า false และ Activity จะปิด (คล้ายกับ super.onBackPressed())
เวอร์ชันโอเวอร์โหลด popBackStack(route: String, inclusive: Boolean) จะลบรายการทั้งหมดจนถึงเส้นทางที่ระบุ หาก inclusive = true เส้นทางที่ระบุจะถูกลบด้วย เมธอดคืนค่า Boolean — true หากพบและลบรายการ เวอร์ชัน inclusive มีประโยชน์สำหรับสถานการณ์ «ออกไปยังหน้าจอหลัก» หลังจากการอนุญาตหรือทำรายการเสร็จสมบูรณ์
| เมธอด | คำอธิบาย | ตัวอย่าง |
|---|---|---|
| popBackStack() | กลับไปหนึ่งหน้าจอ | navController.popBackStack() |
| popBackStack(route, false) | ล้างถึง route (route ยังคงอยู่) | popBackStack(“home”, false) |
| popBackStack(route, true) | ล้างถึงและรวม route | popBackStack(“home”, true) |
| navigate(route) { popUpTo(route) { inclusive = true } } | นำทางด้วยการล้างทั้งหมด | navigate(“login”) { popUpTo(0) { inclusive = true } } |
ในการจัดการปุ่ม Back ของระบบ (hardware back button) ให้ใช้ BackHandler จาก Compose BackHandler รับ enabled และ onBack — callback ที่เรียกเมื่อกด สำหรับ Android 14+ จะใช้ PredictiveBackGesture ซึ่งรวมผ่าน NavController ตั้งแต่เวอร์ชัน 2.9.0 Predictive back เพิ่มแอนิเมชันแสดงตัวอย่างการกลับมา
SavedStateHandle เป็นกลไกในการรักษาสถานะ ViewModel ระหว่างการนำทางและการกำหนดค่าใหม่ NavController จัดหา SavedStateHandle โดยอัตโนมัติสำหรับแต่ละ NavBackStackEntry ผ่าน SavedStateHandle ViewModel จะเก็บสถานะหน้าจอและคืนค่าเมื่อกลับมา (restoreState = true)
ใน Navigation Compose SavedStateHandle ถูกใช้ร่วมกับ ViewModel: ViewModel เริ่มต้นผ่าน SavedStateHandle ซึ่งส่งมาจาก backStackEntry เมื่อนำทางไปยังหน้าจออื่นและกลับมา (ด้วย restoreState) ViewModel จะได้รับสถานะที่บันทึกไว้แทนที่จะถูกสร้างขึ้นใหม่ ซึ่งสำคัญสำหรับหน้าจอที่มีการป้อนข้อมูล ตัวกรอง หรือการเลื่อน
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userId: String = savedStateHandle.get<String>("userId") ?: ""
var searchQuery by savedStateHandle.getStateFlow("search", "")
.collectAsState()
}
SavedStateHandle รองรับประเภทพื้นฐาน String Bundle และ Parcelable สำหรับวัตถุที่ซับซ้อน ให้บันทึกเฉพาะ ID และโหลดข้อมูลแบบเต็มจากพื้นที่เก็บข้อมูล ขีดจำกัดของ SavedStateHandle คือประมาณ 1 MB การเกินจะทำให้เกิด TransactionTooLargeException สำหรับปริมาณมาก ให้ใช้ Room หรือ DataStore แทนการบันทึกใน handle
สำคัญ: SavedStateHandle จะรักษาสถานะเฉพาะเมื่อใช้ restoreState = true ใน NavOptions หากไม่ได้ระบุ restoreState เมื่อกลับมา ViewModel จะถูกสร้างขึ้นใหม่ด้วยค่าเริ่มต้น สำหรับการสลับ BottomNavigation ด้วย restoreState NavController จะรักษาสถานะของแต่ละแท็บและคืนค่าเมื่อเลือกอีกครั้ง
currentBackStackEntryAsState() เป็นฟังก์ชันที่คืนค่า State<NavBackStackEntry?> ซึ่งอัปเดตทุกครั้งที่มีการเปลี่ยนแปลงเส้นทางปัจจุบัน นี่เป็นกลไกหลักสำหรับการซิงโครไนซ์ UI กับการนำทาง: BottomNavigation เน้นรายการที่ใช้งานอยู่ Toolbar อัปเดตชื่อเรื่อง Drawer ปิดเมื่อเปลี่ยน
ฟังก์ชันทำงานผ่าน snapshotFlow และ collectAsState: เมื่อ back stack เปลี่ยนแปลง Compose จะประกอบองค์ประกอบที่สมัครรับข้อมูลใหม่ สำคัญ: currentBackStackEntryAsState() อัปเดตหลังจากแอนิเมชันการเปลี่ยนเสร็จสมบูรณ์เท่านั้น สำหรับการอัปเดตทันที ให้ใช้ currentDestination ซึ่งเปลี่ยนแปลงซิงโครนัสกับ navigate() แต่ไม่สนับสนุนสถานะ
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Text(
text = when (currentRoute) {
"home" -> "Home"
"profile" -> "Profile"
else -> ""
}
)
ในการเข้าถึงอาร์กิวเมนต์ของเส้นทางปัจจุบัน ให้ใช้ navBackStackEntry?.arguments ซึ่งสะดวกใน BottomNavigation: selectedItem ถูกคำนวณตาม currentRoute สำหรับการดีบักการนำทาง ให้ใช้ NavController.addOnDestinationChangedListener() ซึ่งบันทึกทุกการเปลี่ยน ในโพรดักชัน หลีกเลี่ยงการสมัครรับข้อมูลภายใน composable จำนวนมาก — สร้างแหล่งข้อมูลเดียวใน ViewModel และส่ง State ไปยัง UI
คำถามที่พบบ่อย
ในทางเทคนิคได้ แต่ไม่แนะนำ NavController ตัวเดียวรับประกัน back stack ที่สอดคล้องและทำให้การดีบักง่ายขึ้น ตัวควบคุมหลายตัวเหมาะสมสำหรับกราฟที่ซ้อนกันที่มีการนำทางแยกกันเท่านั้น (เช่น modal bottom sheet ที่มีสแต็กของตัวเอง)
ส่ง NavController ไปยัง ViewModel ผ่านคอนสตรักเตอร์หรือ DI อย่างไรก็ตาม ควรส่งเฉพาะฟังก์ชัน callback (onNavigate, onBack) แทนที่จะส่ง NavController เอง — ซึ่งทำให้การทดสอบง่ายขึ้น สำหรับอีเวนต์ ให้ใช้ Channel<NavEvent> ใน ViewModel และรวบรวมใน UI
ปัญหาอยู่ที่วงจรชีวิต: หาก NavController ยังไม่ได้เริ่มต้น (NavHost ยังไม่ได้สร้าง) navigate() จะถูกละเว้น ใช้ LaunchedEffect เพื่อเรียกการนำทางหลังจากโหลดข้อมูล ไม่ใช่ภายใน coroutine ที่มีวงจรชีวิตตามอำเภอใจ
เรียก navController.navigate(“target”) { popUpTo(0) { inclusive = true } } พารามิเตอร์ popUpTo(0) ล้างสแต็กอย่างสมบูรณ์ inclusive = true จะลบรายการเริ่มต้นด้วย แฟล็ก launchSingleTop = true ป้องกันเส้นทางที่ซ้ำกัน
NavHostController เป็นคลาสย่อยของ NavController ที่มีเมธอดเพิ่มเติมสำหรับ NavHost (เช่น setOnBackStackChangedListener) NavController เป็นคลาสพื้นฐานที่สามารถใช้นอก NavHost สำหรับการจัดการสแต็กแบบโปรแกรม ในกรณีส่วนใหญ่จะใช้ NavHostController
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม