คอมโพเนนต์การนำทางรองรับแอปพลิเคชัน Jetpack Compose คุณสามารถไปยังมาใน Composable ได้ในขณะที่ใช้ประโยชน์จากโครงสร้างพื้นฐานและฟีเจอร์ของคอมโพเนนต์ Navigation
ดูไลบรารีการนำทางเวอร์ชันอัลฟ่าล่าสุดที่สร้างขึ้นสำหรับ Compose โดยเฉพาะได้ที่เอกสารประกอบเกี่ยวกับการนำทาง 3
ตั้งค่า
หากต้องการรองรับ Compose ให้ใช้ทรัพยากร Dependency ต่อไปนี้ในไฟล์
build.gradle
ของโมดูลแอป
Groovy
dependencies { def nav_version = "2.9.5" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.9.5" 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
จะแสดงแถบ Bottom Navigation ส่วนในหน้าจอที่ขยายแล้ว จะแสดง Navigation Rail แทน
ดูข้อมูลเพิ่มเติมได้ที่สร้างการนำทางแบบปรับเปลี่ยนได้
ความสามารถในการทำงานร่วมกัน
หากต้องการใช้คอมโพเนนต์การนำทางกับ 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
แลมบ์ดาให้ไว้จะช่วยให้คุณ
แยกโค้ดการนำทางออกจาก 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
ให้เพิ่มการอ้างอิงการทดสอบการนำทางต่อไปนี้
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 จึงเป็นวิธีที่ แนะนำ หากต้องการดูวิธีทดสอบฟังก์ชันนี้ควบคู่ไปกับฟังก์ชันที่ใช้ร่วมกันได้แต่ละฟังก์ชัน แบบแยกกัน โปรดดู 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 ได้ที่เริ่มต้นใช้งานคอมโพเนนต์การนำทางหรือทำตามCodelab การนำทางของ Jetpack Compose
ดูวิธีออกแบบการนำทางของแอปให้ปรับตามขนาดหน้าจอ การวางแนว และรูปแบบต่างๆ ได้ที่การนำทางสำหรับ UI ที่ปรับเปลี่ยนตามอุปกรณ์
หากต้องการดูข้อมูลเกี่ยวกับการติดตั้งใช้งานการนำทาง Compose ขั้นสูงเพิ่มเติมในแอปแบบแยกส่วน รวมถึงแนวคิดต่างๆ เช่น กราฟที่ซ้อนกันและการผสานรวมแถบนำทางด้านล่าง ให้ดูแอป Now in Android ใน GitHub
ตัวอย่าง
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- Material Design 2 ใน Compose
- ย้ายข้อมูล Jetpack Navigation ไปยัง Navigation Compose
- ตำแหน่งที่จะยก สถานะ