คอมโพเนนต์การนำทางจะรองรับแอปพลิเคชัน 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 ที่เหมาะสมจากคอมโพสิเบิลได้ด้วย
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
Groovy
dependencies { implementation "androidx.compose.material:material:1.7.5" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.7.5") } 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)
)
ใน Composable ของ BottomNavigation
ให้รับ NavBackStackEntry
ปัจจุบันโดยใช้ฟังก์ชัน currentBackStackEntryAsState()
รายการนี้จะช่วยให้คุณเข้าถึง NavDestination
ปัจจุบันได้ จากนั้น คุณจะระบุสถานะที่เลือกของแต่ละ BottomNavigationItem
ได้โดยการเปรียบเทียบเส้นทางของรายการกับเส้นทางของปลายทางปัจจุบันและปลายทางระดับบนสุดเพื่อจัดการกรณีต่างๆ เมื่อใช้การนำทางที่ซ้อนกันโดยใช้ลำดับชั้น NavDestination
นอกจากนี้ ระบบยังใช้เส้นทางของรายการเพื่อเชื่อมต่อ Lambda onClick
กับการเรียกใช้ navigate
เพื่อให้การแตะรายการนําไปยังรายการนั้น เมื่อใช้ Flag 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
จะมีสถานะล่าสุดโดยอัตโนมัติ
ความสามารถในการทำงานร่วมกัน
หากต้องการใช้คอมโพเนนต์การนำทางกับ Compose คุณมี 2 ตัวเลือกดังนี้
- กำหนดกราฟการนำทางด้วยคอมโพเนนต์การนำทางสำหรับส่วนย่อย
- กําหนดกราฟการนําทางด้วย
NavHost
ในคอมโพซโดยใช้ปลายทางคอมโพซ ซึ่งจะทำได้ในกรณีที่หน้าจอทั้งหมดในกราฟการนําทางเป็นแบบคอมโพสิเบิลเท่านั้น
ดังนั้น คําแนะนําสําหรับแอป Compose และ Views แบบผสมคือให้ใช้คอมโพเนนต์การนําทางตาม Fragment จากนั้น Fragment จะระงับหน้าจอแบบมุมมอง หน้าจอเขียน และหน้าจอที่ใช้ทั้งมุมมองและการเขียน เมื่อเนื้อหาของ 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
ไปยังคอมโพสิเบิลโดยตรง แต่ให้ส่งการเรียกกลับการนำทางเป็นพารามิเตอร์แทน ซึ่งจะช่วยให้คอมโพสิเบิลทั้งหมดสามารถทดสอบแยกกันได้ เนื่องจากไม่จําเป็นต้องใช้อินสแตนซ์ของ navController
ในการทดสอบ
ระดับการสื่อให้ทราบโดย composable
lambda คือสิ่งที่ช่วยให้คุณแยกโค้ดการนำทางออกจากคอมโพสิเบิลได้ ซึ่งทำได้ 2 วิธีดังนี้
- ส่งผ่านเฉพาะอาร์กิวเมนต์ที่แยกวิเคราะห์ไปยัง Composable ของคุณ
- ส่ง 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
, การนําทางที่ส่งไปยังคอมโพสิเบิล รวมถึงคอมโพสิเบิลหน้าจอแต่ละรายการ
การทดสอบ 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
เพื่อตรวจสอบการยืนยันได้โดยเปรียบเทียบเส้นทางปัจจุบันกับเส้นทางที่คาดไว้ โดยใช้ navController
's currentBackStackEntry
ดังนี้
@Test
fun appNavHost_clickAllProfiles_navigateToProfiles() {
composeTestRule.onNodeWithContentDescription("All Profiles")
.performScrollTo()
.performClick()
assertTrue(navController.currentBackStackEntry?.destination?.hasRoute<Profile>() ?: false)
}
ดูคําแนะนําเพิ่มเติมเกี่ยวกับพื้นฐานการทดสอบ Compose ได้ที่การทดสอบเลย์เอาต์ Compose และการทดสอบใน Jetpack Compose ของ Codelab ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบขั้นสูงของโค้ดการนำทางได้ที่คู่มือทดสอบการนําทาง
ดูข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการนำทางของ Jetpack ได้ที่เริ่มต้นใช้งานคอมโพเนนต์การนำทาง หรือใช้ Codelab การนำทางของ Jetpack Compose
ดูวิธีออกแบบการนําทางของแอปให้ปรับขนาด การวางแนว และรูปแบบของหน้าจอต่างๆ ได้ได้ที่การนําทางสําหรับ UI ที่ปรับเปลี่ยนตามอุปกรณ์
หากต้องการดูข้อมูลเกี่ยวกับการใช้งานการไปยังส่วนต่างๆ ของ Compose ขั้นสูงขึ้นในแอปที่แยกเป็นโมดูล รวมถึงแนวคิดต่างๆ เช่น กราฟที่ซ้อนกันและการผสานรวมแถบนำทางด้านล่าง ลองดูที่แอป Now in Android บน GitHub
ตัวอย่าง
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- Material Design 2 ในเครื่องมือเขียน
- ย้ายข้อมูลการนำทางใน Jetpack ไปยังฟีเจอร์ช่วยเขียนในการไปยังส่วนต่างๆ
- วิธีสร้างสถานะ