0Pricing
Android Academy · บทเรียน

การนำทางข้ามโมดูล

เชื่อมต่อฟีเจอร์โดยไม่สร้างการผูกติด

การนำทางข้ามโมดูล เป็นบทเรียน Android Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Android Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน

ความท้าทายด้านการนำทาง

เมื่อฟีเจอร์อยู่ในโมดูลแยกกัน จะเกิดคำถามใหม่ว่า :feature:home จะเปิดหน้าจอใน :feature:profile ได้อย่างไรโดย ไม่ขึ้นต่อกับโมดูลนั้น หากฟีเจอร์นำเข้าซึ่งกันและกันโดยตรง ก็จะเกิดการเชื่อมโยงแน่นหนาและเสี่ยงต่อการเกิดวงจร

ในบทเรียนนี้ คุณจะเชื่อมฟีเจอร์ต่าง ๆ ผ่านการนำทาง โดยยังคงให้ฟีเจอร์เป็นอิสระต่อกัน

NavHost อยู่ที่ใด

NavHost เพียงหนึ่งเดียวจะอยู่ใน โมดูล :app ซึ่งเป็นสถานที่เดียวที่ได้รับอนุญาตให้รู้จักฟีเจอร์ทั้งหมด แต่ละฟีเจอร์จะเพิ่มปลายทางของตนเอง แล้ว :app จะประกอบปลายทางเหล่านั้นเป็นกราฟเดียว

// app/AppNavHost.kt
@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
    NavHost(navController, startDestination = HomeRoute) {
        homeScreen(onProfileClick = { navController.navigate(ProfileRoute) })
        profileScreen(onBack = { navController.popBackStack() })
    }
}

เส้นทางที่ปลอดภัยด้านชนิดข้อมูล

Navigation Compose รุ่นใหม่รองรับ เส้นทางที่ปลอดภัยด้านชนิดข้อมูล โดยเส้นทางจะเป็นออบเจ็กต์หรือคลาสข้อมูล @Serializable ไม่ใช่สตริงลึกลับ แต่ละฟีเจอร์จะกำหนดชนิดเส้นทางของตนเองในโมดูล เพื่อให้ฟีเจอร์เป็นเจ้าของสัญญาการนำทางของตน

// feature/profile/ProfileRoute.kt
import kotlinx.serialization.Serializable

@Serializable
data class ProfileRoute(val userId: String)

ฟีเจอร์เปิดเผยส่วนขยายของ NavGraphBuilder

เคล็ดลับสำคัญคือ แต่ละฟีเจอร์จะเปิดเผย ฟังก์ชันส่วนขยายของ NavGraphBuilder เพื่อลงทะเบียนปลายทาง ฟีเจอร์เป็นเจ้าของหน้าจอของตนเอง ส่วน :app เพียงเรียกใช้ฟังก์ชันนี้ ฟีเจอร์จะไม่อ้างอิงฟีเจอร์อื่น

// feature/profile/ProfileNavigation.kt
fun NavGraphBuilder.profileScreen(onBack: () -> Unit) {
    composable<ProfileRoute> { backStackEntry ->
        val route: ProfileRoute = backStackEntry.toRoute()
        ProfileScreen(userId = route.userId, onBack = onBack)
    }
}

ลดการเชื่อมโยงด้วยการเรียกกลับจากการนำทาง

ฟีเจอร์ต้องไม่เรียก navigate(SomeOtherFeatureRoute) โดยตรง เพราะจะทำให้ต้องขึ้นต่อกับฟีเจอร์อื่น แต่ฟีเจอร์ควรเปิดเผย การเรียกกลับแบบแลมบ์ดา เช่น onProfileClick แทน โมดูล :app จะเป็นผู้ตัดสินใจว่าการเรียกกลับเหล่านั้นจะนำไปที่ใด

// feature/home/HomeNavigation.kt
fun NavGraphBuilder.homeScreen(onProfileClick: (String) -> Unit) {
    composable<HomeRoute> {
        HomeScreen(onUserClick = { userId -> onProfileClick(userId) })
    }
}
// :home does NOT know ProfileRoute exists.

โมดูล :app เชื่อมต่อทุกอย่าง

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

// app/AppNavHost.kt
NavHost(navController, startDestination = HomeRoute) {
    homeScreen(
        onProfileClick = { userId ->
            navController.navigate(ProfileRoute(userId)) // app knows both
        }
    )
    profileScreen(onBack = { navController.popBackStack() })
}

ส่งอาร์กิวเมนต์อย่างปลอดภัย

เนื่องจากเส้นทางเป็นคลาสข้อมูล @Serializable อาร์กิวเมนต์จึงได้รับการตรวจสอบชนิดข้อมูลขณะคอมไพล์ คุณอ่านค่ากลับได้ด้วย toRoute() ภายในปลายทาง ไม่ต้องแยกวิเคราะห์สตริงหรือพบข้อขัดข้องขณะทำงานจาก arguments?.getString(...) อีกต่อไป

// Reading arguments inside the destination
composable<ProfileRoute> { entry ->
    val args: ProfileRoute = entry.toRoute()
    ProfileScreen(userId = args.userId)
}

// Or directly in a ViewModel via SavedStateHandle
val route: ProfileRoute = savedStateHandle.toRoute()

ใช้ชนิดเส้นทางร่วมกันผ่านโมดูล api

บางครั้งฟีเจอร์หนึ่งจำเป็นต้องนำทางไปยังอีกฟีเจอร์หนึ่งจริง ๆ และต้องการชนิดเส้นทาง แทนที่จะขึ้นต่อกับฟีเจอร์ทั้งชุด ให้เปิดเผยเฉพาะเส้นทางในโมดูล :feature:profile:api ขนาดเล็กที่มีเพียงเส้นทาง @Serializable โมดูล :impl ขนาดใหญ่จะยังคงเป็นส่วนตัว

// feature/profile/api  -> only the route type
@Serializable
data class ProfileRoute(val userId: String)

// feature/home/build.gradle.kts
// home may depend on the lightweight api to build the route,
// but never on :feature:profile:impl
implementation(project(":feature:profile:api"))

การนำทางซ้อนกันต่อฟีเจอร์

ฟีเจอร์ที่มีหลายหน้าจอสามารถเปิดเผย กราฟซ้อน ทั้งชุดโดยใช้ navigation<T> ฟีเจอร์จะเป็นเจ้าของกระบวนการภายในของตนเอง ส่วน :app เพียงติดตั้งกราฟไว้ที่จุดเริ่มต้นเดียว

// feature/onboarding/OnboardingNavigation.kt
fun NavGraphBuilder.onboardingGraph(onFinished: () -> Unit) {
    navigation<OnboardingGraph>(startDestination = WelcomeRoute) {
        composable<WelcomeRoute> { WelcomeScreen() }
        composable<PermissionsRoute> { PermissionsScreen(onDone = onFinished) }
    }
}

ลิงก์เชิงลึกข้ามโมดูล

เส้นทางที่ปลอดภัยด้านชนิดข้อมูลยังรองรับ ลิงก์เชิงลึก ฟีเจอร์จะประกาศรูปแบบ URI ของลิงก์เชิงลึกสำหรับปลายทางของตน จากนั้น NavHost ของ :app จะจับคู่ลิงก์ขาเข้ากับหน้าจอของฟีเจอร์ที่ถูกต้อง โดยไม่ต้องนำเข้าฟีเจอร์ข้ามกัน

composable<ProfileRoute>(
    deepLinks = listOf(
        navDeepLink<ProfileRoute>(basePath = "https://myapp.com/profile")
    )
) { entry ->
    ProfileScreen(userId = entry.toRoute<ProfileRoute>().userId)
}

สรุปรูปแบบการลดการเชื่อมโยง

เมื่อนำมารวมกัน กฎต่าง ๆ ก็เรียบง่าย:

  • แต่ละฟีเจอร์เป็นเจ้าของ ชนิดเส้นทางและ ส่วนขยายของ NavGraphBuilder ของตนเอง
  • ฟีเจอร์สื่อสารเจตนาผ่าน การเรียกกลับแบบแลมบ์ดา ไม่ใช่การนำทางโดยตรง
  • มีเพียง :app ที่รู้จักฟีเจอร์ทั้งหมดและเชื่อมการเรียกกลับเข้ากับเส้นทางจริง
  • หากต้องใช้เส้นทางร่วมกัน ให้เปิดเผยผ่านโมดูล :api ขนาดเล็ก

วิธีนี้ทำให้ฟีเจอร์เป็นอิสระต่อกัน ใช้แคชการสร้างได้ และไม่มีวงจร

ตรวจสอบอย่างรวดเร็ว

ในแอปที่มีหลายโมดูล :feature:home ต้องส่งผู้ใช้ไปยังหน้าจอใน :feature:profile วิธีใดสะอาดที่สุดในการทำให้ฟีเจอร์ไม่เชื่อมโยงกัน

สรุป: การนำทางข้ามโมดูล

คุณได้เรียนรู้วิธีเชื่อมฟีเจอร์โดยไม่ทำให้ฟีเจอร์ขึ้นต่อกัน:

  • NavHost เพียงหนึ่งเดียวจะอยู่ในโมดูล :app แบบบาง
  • แต่ละฟีเจอร์เป็นเจ้าของเส้นทาง @Serializable และส่วนขยาย NavGraphBuilder ของตนเอง
  • ฟีเจอร์เปิดเผย การเรียกกลับ แทนการนำทางไปยังฟีเจอร์อื่นโดยตรง
  • แบ่งปันเส้นทางผ่านโมดูล :api ขนาดเล็กเฉพาะเมื่อจำเป็นจริง ๆ ส่วนลิงก์เชิงลึกและกราฟซ้อนก็ใช้รูปแบบเดียวกัน

เท่านี้ก็จบบทสถาปัตยกรรมแอปแบบหลายโมดูล: ตอนนี้คุณสามารถแยก เชื่อมต่อ และนำทางในโค้ดเบส Android ที่ขยายขนาดได้แล้ว

คำถามที่พบบ่อย

บทเรียน “การนำทางข้ามโมดูล” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การนำทางข้ามโมดูล” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Android Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การนำทางข้ามโมดูล”

เชื่อมต่อฟีเจอร์โดยไม่สร้างการผูกติด คุณปฏิบัติ Android Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Android Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Android Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การนำทางข้ามโมดูล” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Android Academy นี้ได้ไหม

ได้ บทเรียน Android Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. เหตุใดจึงควรแยกเป็นโมดูล
  2. โมดูลฟีเจอร์และโมดูลแกนกลาง
  3. การจัดการการพึ่งพาระหว่างโมดูล
  4. การนำทางข้ามโมดูล
← กลับไปที่ Android Academy