คอมโพเนนต์การนำทางรองรับแอปพลิเคชัน Jetpack Compose คุณสามารถนำทางไปมาระหว่าง Composable ต่างๆ ขณะที่ใช้ประโยชน์จากโครงสร้างพื้นฐานและฟีเจอร์ของคอมโพเนนต์การนำทาง
ดูไลบรารีการนำทางเวอร์ชันก่อนเผยแพร่ล่าสุดที่สร้างขึ้นสำหรับ Compose โดยเฉพาะได้ที่เอกสารประกอบ Navigation 3
ตั้งค่า
หากต้องการรองรับ Compose ให้ใช้ทรัพยากร Dependency ต่อไปนี้ในไฟล์ build.gradle ของโมดูลแอป
Groovy
dependencies { def nav_version = "2.9.7" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.9.7" implementation("androidx.navigation:navigation-compose:$nav_version") }
เริ่มต้นใช้งาน
เมื่อใช้การนำทางในแอป ให้ใช้โฮสต์ กราฟ และ ตัวควบคุมการนำทาง ดูข้อมูลเพิ่มเติมได้ที่ภาพรวมการนำทาง
สร้าง NavController
ดูข้อมูลเกี่ยวกับวิธีสร้าง NavController ใน Compose ได้ที่ส่วน Compose ของสร้างตัวควบคุมการนำทาง
สร้าง NavHost
ดูข้อมูลเกี่ยวกับวิธีสร้าง NavHost ใน Compose ได้ที่ส่วน Compose
ของออกแบบกราฟการนำทาง
ไปยัง Composable
ดูข้อมูลเกี่ยวกับการไปยัง Composable ได้ที่หัวข้อไปยังปลายทางในเอกสารประกอบเกี่ยวกับสถาปัตยกรรม
ไปยังส่วนต่างๆ ด้วยอาร์กิวเมนต์
ดูข้อมูลเกี่ยวกับการส่งอาร์กิวเมนต์ระหว่างปลายทางที่ใช้ Composable ได้ที่ส่วน Compose ของออกแบบกราฟการนำทาง
ดึงข้อมูลที่ซับซ้อนเมื่อนำทาง
เราขอแนะนำอย่างยิ่งว่าอย่าส่งออบเจ็กต์ข้อมูลที่ซับซ้อนเมื่อไปยังส่วนต่างๆ แต่ให้ส่งข้อมูลที่จำเป็นขั้นต่ำแทน เช่น ตัวระบุที่ไม่ซ้ำกัน หรือรหัสรูปแบบอื่นๆ เป็นอาร์กิวเมนต์เมื่อดำเนินการนำทาง
// Pass only the user ID when navigating to a new destination as argument
navController.navigate(Profile(id = "user1234"))
ควรจัดเก็บออบเจ็กต์ที่ซับซ้อนเป็นข้อมูลในแหล่งความจริงเดียว เช่น
ชั้นข้อมูล เมื่อไปถึงปลายทางหลังจากไปยังส่วนต่างๆ แล้ว คุณจะ
โหลดข้อมูลที่จำเป็นจากแหล่งข้อมูลที่เชื่อถือได้เพียงแหล่งเดียวได้โดยใช้รหัสที่ส่งมา
หากต้องการดึงอาร์กิวเมนต์ใน ViewModel ที่รับผิดชอบในการเข้าถึงชั้นข้อมูล ให้ใช้ SavedStateHandle ของ ViewModel ดังนี้
class UserViewModel(
savedStateHandle: SavedStateHandle,
private val userInfoRepository: UserInfoRepository
) : ViewModel() {
private val profile = savedStateHandle.toRoute<Profile>()
// Fetch the relevant user information from the data layer,
// ie. userInfoRepository, based on the passed userId argument
private val userInfo: Flow<UserInfo> = userInfoRepository.getUserInfo(profile.id)
// …
}
วิธีนี้จะช่วยป้องกันไม่ให้ข้อมูลสูญหายระหว่างการเปลี่ยนแปลงการกำหนดค่าและความไม่สอดคล้องกันเมื่อมีการอัปเดตหรือเปลี่ยนแปลงออบเจ็กต์ที่เป็นปัญหา
ดูคำอธิบายแบบเจาะลึกเพิ่มเติมเกี่ยวกับสาเหตุที่คุณควรหลีกเลี่ยงการส่งข้อมูลที่ซับซ้อนเป็นอาร์กิวเมนต์ รวมถึงรายการประเภทอาร์กิวเมนต์ที่รองรับได้ที่ส่งข้อมูลระหว่างปลายทาง
Deep Link
Navigation Compose รองรับ Deep Link ที่กำหนดเป็นส่วนหนึ่งของฟังก์ชัน composable() ได้ด้วย พารามิเตอร์ deepLinks ของออบเจ็กต์ยอมรับรายการ
NavDeepLink ซึ่งสร้างได้โดยใช้เมธอด navDeepLink()
@Serializable data class Profile(val id: String)
val uri = "https://www.example.com"
composable<Profile>(
deepLinks = listOf(
navDeepLink<Profile>(basePath = "$uri/profile")
)
) { backStackEntry ->
ProfileScreen(id = backStackEntry.toRoute<Profile>().id)
}
Deep Link เหล่านี้ช่วยให้คุณเชื่อมโยง URL, การดำเนินการ หรือประเภท MIME ที่เฉพาะเจาะจงกับ
Composable ได้ โดยค่าเริ่มต้น Deep Link เหล่านี้จะไม่แสดงต่อแอปภายนอก หากต้องการ
ทำให้ Deep Link เหล่านี้พร้อมใช้งานภายนอก คุณต้องเพิ่มองค์ประกอบ <intent-filter> ที่เหมาะสมลงในไฟล์ manifest.xml ของแอป หากต้องการเปิดใช้ Deep Link ในตัวอย่างก่อนหน้า คุณควรเพิ่มข้อมูลต่อไปนี้ภายในองค์ประกอบ <activity> ของไฟล์ Manifest
<activity …>
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
</intent-filter>
</activity>
การนำทางจะทำ Deep Link ไปยัง Composable นั้นโดยอัตโนมัติเมื่อแอปอื่นทริกเกอร์ Deep Link
นอกจากนี้ คุณยังใช้ Deep Link เดียวกันนี้เพื่อสร้าง PendingIntent ด้วย
Deep Link ที่เหมาะสมจาก Composable ได้ด้วย
val id = "exampleId"
val context = LocalContext.current
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"https://www.example.com/profile/$id".toUri(),
context,
MyActivity::class.java
)
val deepLinkPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
จากนั้นคุณจะใช้ deepLinkPendingIntent นี้ได้เหมือนกับ PendingIntent อื่นๆ เพื่อ
เปิดแอปที่ปลายทางของ Deep Link
การนำทางแบบซ้อน
ดูข้อมูลเกี่ยวกับวิธีสร้างกราฟการนำทางที่ซ้อนกันได้ที่ กราฟที่ซ้อนกัน
สร้างแถบนำทางและแถบข้างนำทางแบบปรับได้
NavigationSuiteScaffold จะแสดง UI การนำทางระดับบนสุดที่เหมาะสมสำหรับแอปของคุณตาม WindowSizeClass ปัจจุบัน สำหรับหน้าจอขนาดกะทัดรัด โครงสร้างจะแสดงแถบนำทางด้านล่าง ส่วนหน้าจอขนาดกลางและขนาดใหญ่จะแสดงแถบนำทาง
NavigationSuiteScaffoldจัดการการนำทางหลัก แต่เลย์เอาต์ที่ปรับเปลี่ยนตามพื้นที่
มักเกี่ยวข้องกับ Composable อื่นๆ ที่มีความเชี่ยวชาญเฉพาะ สำหรับเลย์เอาต์มาตรฐานของรายการ-รายละเอียดและแผงสนับสนุน
ซึ่งพบได้ทั่วไปในการออกแบบแบบปรับอัตโนมัติ ให้ใช้
ListDetailPaneScaffold และ SupportingPaneScaffold ตามลำดับ
ดูข้อมูลเพิ่มเติมได้ที่สร้างเลย์เอาต์ที่ปรับเปลี่ยนตามอุปกรณ์
ความสามารถในการทำงานร่วมกัน
หากต้องการใช้คอมโพเนนต์การนำทางกับ Compose คุณมี 2 ตัวเลือกดังนี้
- กำหนดกราฟการนำทางด้วยคอมโพเนนต์การนำทางสำหรับ Fragment
- กำหนดกราฟการนำทางด้วย
NavHostใน Compose โดยใช้ปลายทาง Compose ซึ่งจะทำได้ก็ต่อเมื่อหน้าจอทั้งหมดในกราฟการนำทางเป็น Composable
ดังนั้น คำแนะนำสำหรับแอปที่ใช้ทั้ง Compose และ View คือการใช้คอมโพเนนต์การนำทางที่อิงตาม Fragment จากนั้น Fragment จะมีหน้าจอที่อิงตาม View หน้าจอ Compose และหน้าจอที่ใช้ทั้ง View และ Compose เมื่อเนื้อหาของแต่ละ Fragment อยู่ใน Compose แล้ว ขั้นตอนถัดไปคือการเชื่อมหน้าจอทั้งหมดเข้าด้วยกันด้วย Navigation Compose และนำ Fragment ทั้งหมดออก
ไปยังส่วนต่างๆ จาก Compose ด้วย Navigation สำหรับ Fragment
หากต้องการเปลี่ยนปลายทางภายในโค้ด Compose คุณต้องเปิดเผยเหตุการณ์ที่ส่งไปยังและทริกเกอร์โดยฟังก์ชันที่ประกอบกันได้ใดก็ได้ในลำดับชั้น
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
ใน Fragment คุณจะสร้างการเชื่อมต่อระหว่าง Compose กับคอมโพเนนต์การนำทางที่อิงตาม Fragment
โดยการค้นหา NavController และไปยังปลายทาง
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
หรือจะส่ง NavController ลงในลำดับชั้นของ Compose ก็ได้
อย่างไรก็ตาม การเปิดเผยฟังก์ชันจะนำกลับมาใช้ใหม่และทดสอบได้มากกว่า
การทดสอบ
แยกโค้ดการนำทางออกจากปลายทางที่ใช้ Composable เพื่อให้ทดสอบ
แต่ละ Composable แยกกันได้ โดยแยกจาก Composable ของ NavHost
ซึ่งหมายความว่าคุณไม่ควรส่ง navController โดยตรงไปยัง
Composable ใดๆ แต่ควรส่งการเรียกกลับการนำทางเป็นพารามิเตอร์แทน ซึ่งช่วยให้ทดสอบ Composable ทั้งหมดได้ทีละรายการ เนื่องจากไม่จำเป็นต้องมีอินสแตนซ์ของ navController ในการทดสอบ
ระดับการเปลี่ยนเส้นทางที่ composable lambda มอบให้คือสิ่งที่ช่วยให้คุณ
แยกโค้ดการนำทางออกจาก Composable เอง ซึ่งทำได้ 2 วิธีดังนี้
- ส่งเฉพาะอาร์กิวเมนต์ที่แยกวิเคราะห์แล้วไปยัง Composable
- ส่งผ่าน Lambda ที่ควรทริกเกอร์โดย Composable เพื่อไปยังส่วนต่างๆ แทนที่จะเป็น
NavControllerเอง
ตัวอย่างเช่น ProfileScreen Composable ที่รับ userId เป็นอินพุตและ
อนุญาตให้ผู้ใช้ไปยังหน้าโปรไฟล์ของเพื่อนอาจมีลายเซ็นดังนี้
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
ด้วยวิธีนี้ ProfileScreen Composable จะทำงานแยกต่างหากจากการนำทาง
จึงทดสอบแยกกันได้ Lambda composable จะ
ห่อหุ้มตรรกะขั้นต่ำที่จำเป็นในการเชื่อมช่องว่างระหว่าง Navigation
API กับ Composable ของคุณ
@Serializable data class Profile(id: String)
composable<Profile> { backStackEntry ->
val profile = backStackEntry.toRoute<Profile>()
ProfileScreen(userId = profile.id) { friendUserId ->
navController.navigate(route = Profile(id = friendUserId))
}
}
เราขอแนะนำให้เขียนการทดสอบที่ครอบคลุมข้อกำหนดการนำทางของแอปโดยการทดสอบ NavHost, การดำเนินการนำทางที่ส่งไปยัง Composable รวมถึง Composable ของหน้าจอแต่ละหน้า
การทดสอบ NavHost
หากต้องการเริ่มทดสอบ NavHost ให้เพิ่มทรัพยากร Dependency สำหรับการทดสอบการนำทางต่อไปนี้
dependencies {
// ...
androidTestImplementation "androidx.navigation:navigation-testing:$navigationVersion"
// ...
}
ห่อหุ้ม NavHost ของแอปใน Composable ซึ่งยอมรับ NavHostController เป็น
พารามิเตอร์
@Composable
fun AppNavHost(navController: NavHostController){
NavHost(navController = navController){ ... }
}
ตอนนี้คุณสามารถทดสอบ AppNavHost และตรรกะการนำทางทั้งหมดที่กำหนดไว้ภายใน
NavHost โดยส่งอินสแตนซ์ของอาร์ติแฟกต์การทดสอบการนำทาง
TestNavHostController การทดสอบ UI ที่ยืนยันปลายทางเริ่มต้นของ
แอปและ NavHost จะมีลักษณะดังนี้
class NavigationTest {
@get:Rule
val composeTestRule = createComposeRule()
lateinit var navController: TestNavHostController
@Before
fun setupAppNavHost() {
composeTestRule.setContent {
navController = TestNavHostController(LocalContext.current)
navController.navigatorProvider.addNavigator(ComposeNavigator())
AppNavHost(navController = navController)
}
}
// Unit test
@Test
fun appNavHost_verifyStartDestination() {
composeTestRule
.onNodeWithContentDescription("Start Screen")
.assertIsDisplayed()
}
}
การทดสอบการดำเนินการนำทาง
คุณทดสอบการติดตั้งใช้งานการนำทางได้หลายวิธีโดยคลิกองค์ประกอบ UI แล้วตรวจสอบจุดหมายที่แสดง หรือเปรียบเทียบเส้นทางที่คาดไว้กับเส้นทางปัจจุบัน
เนื่องจากคุณต้องการทดสอบการใช้งานแอปจริง การคลิกใน UI จึงเป็นวิธีที่แนะนำ หากต้องการดูวิธีทดสอบฟังก์ชันนี้ควบคู่ไปกับฟังก์ชัน Composable แต่ละรายการ แบบแยกกัน โปรดดู Codelab การทดสอบใน Jetpack Compose
นอกจากนี้ คุณยังใช้ navController เพื่อตรวจสอบการยืนยันได้โดยเปรียบเทียบ
เส้นทางปัจจุบันกับเส้นทางที่คาดไว้โดยใช้ navController
currentBackStackEntry ดังนี้
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
ดูคำแนะนำเพิ่มเติมเกี่ยวกับพื้นฐานการทดสอบ Compose ได้ที่ การทดสอบเลย์เอาต์ Compose และ Codelab การทดสอบใน Jetpack Compose ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบโค้ดการนำทางขั้นสูงได้ที่คู่มือทดสอบการนำทาง
ดูข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับ Jetpack Navigation ได้ที่เริ่มต้นใช้งานคอมโพเนนต์การนำทาง หรือทำตาม Codelab การนำทาง Jetpack Compose
ดูวิธีออกแบบการไปยังส่วนต่างๆ ของแอปเพื่อให้ปรับเปลี่ยนตามขนาดหน้าจอ การวางแนว และรูปแบบของอุปกรณ์ต่างๆ ได้ที่การไปยังส่วนต่างๆ สำหรับ UI ที่ปรับเปลี่ยนตามอุปกรณ์
ดูข้อมูลเกี่ยวกับการติดตั้งใช้งานการนำทาง Compose ขั้นสูงเพิ่มเติมในแอปแบบแยกส่วน รวมถึงแนวคิดต่างๆ เช่น กราฟที่ซ้อนกันและการผสานรวมแถบนำทางด้านล่างได้ในแอป Now in Android บน GitHub
ตัวอย่าง
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- Material Design 2 ใน Compose
- ย้ายข้อมูล Jetpack Navigation ไปยัง Navigation Compose
- ตำแหน่งที่จะยก สถานะ