การไปยังส่วนต่างๆ ด้วยการเขียน

คอมโพเนนต์ การนำทาง รองรับแอปพลิเคชัน Jetpack Compose คุณสามารถนำทางไปมาระหว่าง Composable ต่างๆ ขณะที่ใช้ประโยชน์จากโครงสร้างพื้นฐานและฟีเจอร์ของคอมโพเนนต์การนำทาง

หากต้องการใช้ไลบรารีการนำทางเวอร์ชันก่อนเผยแพร่ล่าสุดที่สร้างขึ้นสำหรับ Compose โดยเฉพาะ โปรดดู เอกสารประกอบการนำทาง 3

ตั้งค่า

หากต้องการรองรับ Compose ให้ใช้ทรัพยากร Dependency ต่อไปนี้ในไฟล์ build.gradle ของโมดูลแอป

Groovy

dependencies {
    def nav_version = "2.9.8"

    implementation "androidx.navigation:navigation-compose:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.9.8"

    implementation("androidx.navigation:navigation-compose:$nav_version")
}

เริ่มต้นใช้งาน

เมื่อใช้การนำทางในแอป ให้ใช้โฮสต์ กราฟ และตัวควบคุมการนำทาง ดูข้อมูลเพิ่มเติมได้ที่ภาพรวมการนำทาง

ดูข้อมูลเกี่ยวกับวิธีสร้าง NavController ใน Compose ได้ที่ส่วน Compose ของหัวข้อสร้างตัวควบคุมการนำทาง

สร้าง NavHost

ดูข้อมูลเกี่ยวกับวิธีสร้าง NavHost ใน Compose ได้ที่ส่วน Compose ของ ออกแบบกราฟการนำทาง

ดูข้อมูลเกี่ยวกับการไปยัง Composable ได้ที่ หัวข้อไปยังปลายทางในเอกสารประกอบสถาปัตยกรรม

ดูข้อมูลเกี่ยวกับการส่งอาร์กิวเมนต์ระหว่างปลายทาง Composable ได้ที่ ส่วน Compose ของ หัวข้อออกแบบกราฟการนำทาง

ดึงข้อมูลที่ซับซ้อนเมื่อไปยังส่วนต่างๆ

เราขอแนะนำอย่างยิ่งว่าอย่าส่งออบเจ็กต์ข้อมูลที่ซับซ้อนเมื่อไปยังส่วนต่างๆ แต่ให้ส่งข้อมูลที่จำเป็นขั้นต่ำ เช่น ตัวระบุที่ไม่ซ้ำกันหรือรหัสรูปแบบอื่นๆ เป็นอาร์กิวเมนต์เมื่อดำเนินการการทำงานเพื่อนำทาง

// Pass only the user ID when navigating to a new destination as argument
navController.navigate(Profile(id = "user1234"))

ควรจัดเก็บออบเจ็กต์ที่ซับซ้อนเป็นข้อมูลในแหล่งข้อมูลที่เชื่อถือได้เพียงแหล่งเดียว (SSOT) เช่น ชั้นข้อมูล เมื่อไปถึงปลายทางหลังจากไปยังส่วนต่างๆ แล้ว คุณจะโหลดข้อมูลที่จำเป็นจากแหล่งข้อมูลเดียวที่เชื่อถือได้โดยใช้รหัสที่ส่งมาได้ หากต้องการดึงอาร์กิวเมนต์ใน 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)

// …

}

แนวทางนี้ช่วยป้องกันการสูญหายของข้อมูลระหว่างการเปลี่ยนแปลงการกำหนดค่าและความไม่สอดคล้องกันเมื่อมีการอัปเดตหรือเปลี่ยนแปลงออบเจ็กต์ที่เป็นปัญหา

ดูคำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับเหตุผลที่คุณควรหลีกเลี่ยงการส่งข้อมูลที่ซับซ้อนเป็น อาร์กิวเมนต์ รวมถึงรายการประเภทอาร์กิวเมนต์ที่รองรับได้ที่หัวข้อส่งข้อมูลระหว่าง ปลายทาง

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 ปัจจุบัน สำหรับหน้าจอขนาดกะทัดรัด Scaffold จะแสดงแถบ Bottom Navigation ส่วนหน้าจอขนาดกลางและขนาดใหญ่จะแสดงแถบข้างนำทาง

NavigationSuiteScaffold จัดการการนำทางหลัก แต่เลย์เอาต์แบบปรับได้มักเกี่ยวข้องกับ Composable เฉพาะอื่นๆ สำหรับเลย์เอาต์มาตรฐานแบบรายละเอียดรายการและแผงสนับสนุน ซึ่งพบได้ทั่วไปในการออกแบบแบบปรับได้ ให้ใช้ ListDetailPaneScaffold และ SupportingPaneScaffold ตามลำดับ ดูข้อมูลเพิ่มเติมได้ที่หัวข้อ สร้างเลย์เอาต์แบบปรับได้

ความสามารถในการทำงานร่วมกัน

หากต้องการใช้คอมโพเนนต์การนำทางกับ Compose คุณมี 2 ตัวเลือกดังนี้

  • กำหนดกราฟการนำทางด้วยคอมโพเนนต์การนำทางสำหรับ Fragment
  • กำหนดกราฟการนำทางด้วย NavHost ใน Compose โดยใช้ปลายทาง Compose ซึ่งจะทำได้ก็ต่อเมื่อหน้าจอทั้งหมดในกราฟการนำทางเป็น Composable

ดังนั้น เราจึงแนะนำให้ใช้คอมโพเนนต์การนำทางที่อิงตาม Fragment สำหรับแอปที่ใช้ทั้ง Compose และ Views จากนั้น Fragment จะเก็บหน้าจอที่อิงตาม Views, หน้าจอ Compose และหน้าจอที่ใช้ทั้ง Views และ Compose เมื่อเนื้อหาของ Fragment แต่ละรายการอยู่ใน Compose แล้ว ขั้นตอนถัดไปคือการเชื่อมหน้าจอทั้งหมดเข้าด้วยกันด้วย Navigation Compose และนำ Fragment ทั้งหมดออก

หากต้องการเปลี่ยนปลายทางภายในโค้ด Compose ให้แสดงเหตุการณ์ที่ส่งไปยังและทริกเกอร์โดย Composable ใดก็ได้ในลำดับชั้น

@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
  • ส่งแลมบ์ดาที่ควรทริกเกอร์โดย Composable เพื่อไปยังส่วนต่างๆ แทนที่จะส่ง NavController เอง

ตัวอย่างเช่น Composable ProfileScreen ที่รับ userId เป็นอินพุตและอนุญาตให้ผู้ใช้ไปยังหน้าโปรไฟล์ของเพื่อนอาจมีลายเซ็นดังนี้

@Composable
fun ProfileScreen(
    userId: String,
    navigateToFriendProfile: (friendUserId: String) -> Unit
) {
 
}

วิธีนี้จะช่วยให้ Composable ProfileScreen ทำงานแยกจากการนำทางได้ จึงทดสอบแยกกันได้ แลมบ์ดา composable จะห่อหุ้มตรรกะขั้นต่ำที่จำเป็นในการเชื่อมช่องว่างระหว่าง 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 จึงเป็นวิธีที่แนะนำ หากต้องการดูวิธีทดสอบการใช้งานนี้ควบคู่ไปกับฟังก์ชันที่ประกอบกันได้แต่ละฟังก์ชัน แบบแยกกัน โปรดดู 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 และ Codelab การทดสอบใน Jetpack Compose หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบโค้ดการนำทางขั้นสูง โปรดไปที่ คู่มือการทดสอบการนำทาง

ดูข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Jetpack Navigation ได้ที่ เริ่มต้นใช้งานคอมโพเนนต์การนำทาง หรือทำตาม Codelab การนำทาง Jetpack Compose

ดูวิธีออกแบบการนำทางของแอปเพื่อให้ปรับเปลี่ยนตามขนาดหน้าจอ การวางแนว และรูปแบบของอุปกรณ์ต่างๆ ได้ที่หัวข้อการนำทางสำหรับ UI ที่ปรับเปลี่ยนตามอุปกรณ์

หากต้องการดูข้อมูลเกี่ยวกับการใช้งานการนำทาง Compose ขั้นสูงเพิ่มเติมในแอปแบบแยกส่วน ซึ่งรวมถึงแนวคิดต่างๆ เช่น กราฟที่ซ้อนกันและการผสานรวมแถบนำทางด้านล่าง โปรดดูแอป Now in Android ใน GitHub

ตัวอย่าง