คอมโพเนนต์การนำทางรองรับแอปพลิเคชัน Jetpack Compose คุณสามารถไปยัง Composable ระหว่างที่ใช้ประโยชน์จาก โครงสร้างพื้นฐานและฟีเจอร์ของคอมโพเนนต์การนำทาง
ตั้งค่า
หากต้องการรองรับ Compose ให้ใช้ Dependency ต่อไปนี้ในไฟล์ build.gradle
ของโมดูลแอป
ดึงดูด
dependencies { def nav_version = "2.8.0" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.8.0" implementation("androidx.navigation:navigation-compose:$nav_version") }
เริ่มต้นใช้งาน
เมื่อใช้การนําทางในแอป ให้ใช้โฮสต์การนําทาง กราฟ และตัวควบคุมการนําทาง ดูข้อมูลเพิ่มเติมได้ที่ภาพรวมการนําทาง
สร้าง NavController
ดูข้อมูลเกี่ยวกับวิธีสร้าง NavController
ใน "เขียน" ได้ที่ส่วน "เขียน" ของสร้างตัวควบคุมการนําทาง
สร้าง NavHost
ดูข้อมูลเกี่ยวกับวิธีสร้าง NavHost
ใน "เขียน" ได้ที่ส่วน "เขียน" ของออกแบบกราฟการนําทาง
ไปที่คอมโพสิเบิล
ดูข้อมูลเกี่ยวกับการไปยัง Composable ได้ที่หัวข้อนำทางไปยังปลายทางในเอกสารประกอบของสถาปัตยกรรม
ไปยังส่วนต่างๆ ด้วยอาร์กิวเมนต์
ดูข้อมูลเกี่ยวกับการส่งอาร์กิวเมนต์ระหว่างปลายทางแบบคอมโพสิเบิลได้ที่ส่วนคอมโพสิเบิลของออกแบบกราฟการนําทาง
เรียกข้อมูลที่ซับซ้อนขณะนำทาง
ขอแนะนำอย่างยิ่งว่าอย่าส่งออบเจ็กต์ข้อมูลที่ซับซ้อนเมื่อไปยังส่วนต่างๆ แต่ให้ส่งข้อมูลที่จำเป็นขั้นต่ำ เช่น ตัวระบุที่ไม่ซ้ำกันหรือรหัสรูปแบบอื่นๆ แทน โดยใช้เป็นอาร์กิวเมนต์เมื่อดำเนินการไปยังส่วนต่างๆ
// 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 ที่เฉพาะเจาะจงกับคอมโพสิเบิลได้ โดยค่าเริ่มต้น 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
นอกจากนี้ 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
การนำทางแบบซ้อน
ดูข้อมูลเกี่ยวกับวิธีสร้างกราฟการนําทางที่ซ้อนกันได้ที่กราฟที่ซ้อนกัน
การผสานรวมกับแถบนําทางด้านล่าง
การกําหนด NavController
ที่ระดับสูงขึ้นในลําดับชั้นแบบคอมโพสิเบิลจะช่วยให้คุณเชื่อมต่อการนําทางกับคอมโพเนนต์อื่นๆ เช่น คอมโพเนนต์การนําทางด้านล่างได้ ซึ่งจะช่วยให้คุณไปยังส่วนต่างๆ ได้โดยการเลือกไอคอนในแถบด้านล่าง
หากต้องการใช้คอมโพเนนต์ BottomNavigation
และ BottomNavigationItem
ให้เพิ่มการพึ่งพา androidx.compose.material
ลงในแอปพลิเคชัน Android
ดึงดูด
dependencies { implementation "androidx.compose.material:material:1.7.1" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.7.1") } android { buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
หากต้องการลิงก์รายการในแถบนําทางด้านล่างกับเส้นทางในกราฟการนําทาง เราขอแนะนําให้กําหนดคลาส เช่น TopLevelRoute
ที่แสดงที่นี่ ซึ่งมีคลาสเส้นทางและไอคอน
data class TopLevelRoute<T : Any>(val name: String, val route: T, val icon: ImageVector)
จากนั้นใส่เส้นทางเหล่านั้นไว้ในรายการที่ BottomNavigationItem
สามารถใช้ได้
val topLevelRoutes = listOf(
TopLevelRoute("Profile", Profile, Icons.Profile),
TopLevelRoute("Friends", Friends, Icons.Friends)
)
ในคอมโพสิเบิล BottomNavigation
ให้รับ NavBackStackEntry
ในปัจจุบันโดยใช้ฟังก์ชัน currentBackStackEntryAsState()
รายการนี้จะช่วยให้คุณเข้าถึง NavDestination
ปัจจุบันได้ จากนั้น คุณจะระบุสถานะที่เลือกของแต่ละ BottomNavigationItem
ได้โดยการเปรียบเทียบเส้นทางของรายการกับเส้นทางของปลายทางปัจจุบันและปลายทางระดับบนสุดเพื่อจัดการกรณีต่างๆ เมื่อใช้การนำทางที่ซ้อนกันโดยใช้ลำดับชั้น NavDestination
นอกจากนี้ ระบบยังใช้เส้นทางของรายการเพื่อเชื่อมต่อ Lambda onClick
กับการเรียกใช้ navigate
เพื่อให้การแตะรายการนําไปยังรายการนั้น การใช้แฟล็ก saveState
และ restoreState
จะทำให้ระบบบันทึกและกู้คืนสถานะและสแต็กแบ็กของรายการดังกล่าวอย่างถูกต้องเมื่อคุณสลับระหว่างรายการการนำทางด้านล่าง
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
topLevelRoutes.forEach { topLevelRoute ->
BottomNavigationItem(
icon = { Icon(topLevelRoute.icon, contentDescription = topLevelRoute.name) },
label = { Text(topLevelRoute.name) },
selected = currentDestination?.hierarchy?.any { it.hasRoute(topLevelRoute.route::class) } == true,
onClick = {
navController.navigate(topLevelRoute.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}
)
}
}
}
) { innerPadding ->
NavHost(navController, startDestination = Profile, Modifier.padding(innerPadding)) {
composable<Profile> { ProfileScreen(...) }
composable<Friends> { FriendsScreen(...) }
}
}
ในส่วนนี้ คุณใช้ประโยชน์จากNavController.currentBackStackEntryAsState()
เมธอดเพื่อยกสถานะ navController
ออกจากฟังก์ชัน NavHost
และแชร์กับคอมโพเนนต์ BottomNavigation
ซึ่งหมายความว่า BottomNavigation
จะมีสถานะล่าสุดโดยอัตโนมัติ
ความสามารถในการทำงานร่วมกัน
หากต้องการใช้คอมโพเนนต์การนำทางร่วมกับการเขียน คุณมี 2 ตัวเลือก ดังนี้
- กำหนดกราฟการนำทางด้วยคอมโพเนนต์การนำทางสำหรับข้อมูลโค้ด
- กำหนดกราฟการนำทางด้วย
NavHost
ใน Compose โดยใช้ปลายทาง Compose ซึ่งจะทำได้ในกรณีที่หน้าจอทั้งหมดในกราฟการนําทางเป็นแบบคอมโพสิเบิลเท่านั้น
ดังนั้น คำแนะนำสำหรับแอป "เขียน" และ "ดู" แบบผสมคือการใช้คอมโพเนนต์การนำทางตามเศษส่วน จากนั้น ข้อมูลโค้ดจะเก็บหน้าจอแบบ View, หน้าจอเขียน และหน้าจอที่ใช้ทั้ง View และเขียน เมื่อเนื้อหาของ Fragment แต่ละรายการอยู่ใน Compose แล้ว ขั้นตอนถัดไปคือการเชื่อมโยงหน้าจอทั้งหมดเหล่านั้นเข้าด้วยกันด้วย Navigation Compose และนํา Fragment ทั้งหมดออก
ไปยังส่วนต่างๆ จาก Compose ด้วย Navigation สำหรับส่วนย่อย
หากต้องการเปลี่ยนปลายทางภายในโค้ด Compose คุณต้องแสดงเหตุการณ์ที่คอมโพสิเบิลใดก็ได้ในลําดับชั้นสามารถส่งผ่านและทริกเกอร์ได้ ดังนี้
@Composable
fun MyScreen(onNavigate: (Int) -> Unit) {
Button(onClick = { onNavigate(R.id.nav_profile) } { /* ... */ }
}
ในข้อมูลโค้ดโค้ดโค้ด คุณจะสร้างบริดจ์ระหว่าง Compose กับคอมโพเนนต์การนําทางที่อิงตามข้อมูลโค้ดโค้ดโดยค้นหา NavController
และไปยังปลายทาง ดังนี้
override fun onCreateView( /* ... */ ) {
setContent {
MyScreen(onNavigate = { dest -> findNavController().navigate(dest) })
}
}
หรือจะส่ง NavController
ลงตามลําดับชั้นการเขียนก็ได้
อย่างไรก็ตาม การเปิดเผยฟังก์ชันแบบง่ายจะนํากลับมาใช้ซ้ำและทดสอบได้มากกว่า
การทดสอบ
แยกโค้ดการนำทางออกจากปลายทางแบบคอมโพสิเบิลเพื่อเปิดใช้การทดสอบคอมโพสิเบิลแต่ละรายการแยกต่างหากจากคอมโพสิเบิล NavHost
ซึ่งหมายความว่าคุณไม่ควรส่ง navController
ไปยังคอมโพสิเบิลโดยตรง แต่ให้ส่งการเรียกกลับการนำทางเป็นพารามิเตอร์แทน วิธีนี้ช่วยให้ Composable ทั้งหมดทดสอบได้ทีละรายการ เนื่องจากไม่ต้องมีอินสแตนซ์ navController
ในการทดสอบ
ระดับการสื่อให้ทราบโดย composable
lambda คือสิ่งที่ช่วยให้คุณแยกโค้ดการนำทางออกจากคอมโพสิเบิลได้ ซึ่งทำได้ 2 วิธีดังนี้
- ส่งเฉพาะอาร์กิวเมนต์ที่แยกวิเคราะห์แล้วไปยังคอมโพสิเบิล
- ส่ง Lambda ที่คอมโพสิเบิลควรทริกเกอร์เพื่อไปยังส่วนต่างๆ แทน
NavController
ตัวอย่างเช่น คอมโพสิเบิล ProfileScreen
ที่รับ userId
เป็นอินพุตและอนุญาตให้ผู้ใช้ไปยังหน้าโปรไฟล์ของเพื่อนอาจมีลายเซ็นดังนี้
@Composable
fun ProfileScreen(
userId: String,
navigateToFriendProfile: (friendUserId: String) -> Unit
) {
…
}
วิธีนี้ช่วยให้คอมโพสิชัน ProfileScreen
ทํางานได้อิสระจากการนำทาง ซึ่งช่วยให้ทดสอบแยกกันได้ แลมดา composable
จะรวมตรรกะขั้นต่ำที่จำเป็นเพื่อเชื่อมช่องว่างระหว่าง Navigation API กับคอมโพสิเบิล
@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
ของแอปในคอมโพสิเบิลที่ยอมรับ 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
เพื่อตรวจสอบการยืนยันสิทธิ์โดยการเปรียบเทียบเส้นทางปัจจุบันกับเส้นทางที่คาดไว้ โดยใช้ currentBackStackEntry
ของ navController
ดังนี้
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
ดูคําแนะนําเพิ่มเติมเกี่ยวกับพื้นฐานการทดสอบ Compose ได้ที่การทดสอบเลย์เอาต์ Compose และการทดสอบใน Jetpack Compose ของ Codelab หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบโค้ดการนำทางขั้นสูง โปรดไปที่คู่มือทดสอบการนำทาง
ดูข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการไปยังส่วนต่างๆ ใน Jetpack ได้ที่เริ่มต้นใช้งานคอมโพเนนต์การไปยังส่วนต่างๆ หรือเข้าร่วมโค้ดแล็บการไปยังส่วนต่างๆ ใน Jetpack Compose
ดูวิธีออกแบบการนําทางของแอปให้ปรับขนาด การวางแนว และรูปแบบของหน้าจอต่างๆ ได้ได้ที่การนําทางสําหรับ UI ที่ปรับเปลี่ยนตามอุปกรณ์
หากต้องการดูข้อมูลเกี่ยวกับการใช้งานการไปยังส่วนต่างๆ ของ Compose ขั้นสูงขึ้นในแอปที่แยกเป็นโมดูล รวมถึงแนวคิดต่างๆ เช่น กราฟที่ซ้อนกันและการผสานรวมแถบนำทางด้านล่าง ลองดูที่แอป Now in Android บน GitHub
ตัวอย่าง
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- Material Design 2 ในเครื่องมือเขียน
- ย้ายข้อมูล Jetpack Navigation ไปยัง Navigation Compose
- วิธีสร้างสถานะ