คอมโพเนนต์การนำทางรองรับแอปพลิเคชัน Jetpack Compose คุณสามารถไปยังมาใน Composable ได้ในขณะที่ใช้ประโยชน์จากโครงสร้างพื้นฐานและฟีเจอร์ของคอมโพเนนต์ Navigation
ดูไลบรารีการนำทางเวอร์ชันอัลฟ่าล่าสุดที่สร้างขึ้นสำหรับ Compose โดยเฉพาะได้ที่เอกสารประกอบเกี่ยวกับการนำทาง 3
ตั้งค่า
หากต้องการรองรับ Compose ให้ใช้ทรัพยากร Dependency ต่อไปนี้ในไฟล์
build.gradle
ของโมดูลแอป
Groovy
dependencies { def nav_version = "2.9.4" implementation "androidx.navigation:navigation-compose:$nav_version" }
Kotlin
dependencies { val nav_version = "2.9.4" 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
การนำทางแบบซ้อน
ดูข้อมูลเกี่ยวกับวิธีสร้างกราฟการนำทางที่ซ้อนกันได้ที่ กราฟที่ซ้อนกัน
การผสานรวมกับแถบนำทางด้านล่าง
การกำหนด NavController
ในระดับที่สูงขึ้นในลำดับชั้นที่ประกอบได้
จะช่วยให้คุณเชื่อมต่อการนำทางกับคอมโพเนนต์อื่นๆ เช่น คอมโพเนนต์การนำทางด้านล่าง
ได้ การทำเช่นนี้จะช่วยให้คุณไปยังส่วนต่างๆ ได้โดยเลือกไอคอนในแถบด้านล่าง
หากต้องการใช้คอมโพเนนต์ BottomNavigation
และ BottomNavigationItem
ให้เพิ่มการอ้างอิง androidx.compose.material
ลงในแอปพลิเคชัน Android
Groovy
dependencies { implementation "androidx.compose.material:material:1.9.1" } android { buildFeatures { compose true } composeOptions { kotlinCompilerExtensionVersion = "1.5.15" } kotlinOptions { jvmTarget = "1.8" } }
Kotlin
dependencies { implementation("androidx.compose.material:material:1.9.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)
)
ใน Composable ของ 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
จะมีสถานะล่าสุดโดยอัตโนมัติ
ความสามารถในการทำงานร่วมกัน
หากต้องการใช้คอมโพเนนต์การนำทางกับ 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 ทั้งหมดแยกกันได้ เนื่องจากไม่จำเป็นต้องมีอินสแตนซ์ของ navController
ในการทดสอบ
ระดับการเปลี่ยนเส้นทางที่ composable
แลมบ์ดาให้ไว้จะช่วยให้คุณ
แยกโค้ดการนำทางออกจาก Composable เองได้ ซึ่งจะทำงานได้ 2 ทิศทางดังนี้
- ส่งเฉพาะอาร์กิวเมนต์ที่แยกวิเคราะห์แล้วไปยัง Composable
- ส่งผ่าน Lambda ที่ควรทริกเกอร์โดย Composable เพื่อไปยังส่วนต่างๆ แทนที่จะเป็น
NavController
เอง
เช่น ProfileScreen
ที่ใช้ 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
- ตำแหน่งที่จะยก สถานะ