التنقّل بين الوحدات
صِل الميزات من دون ترابط وثيق
التنقّل بين الوحدات درس مجاني في Android Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 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) مباشرةً مطلقًا، لأن ذلك سيتطلب الاعتماد على الميزة الأخرى. بدلًا من ذلك، تكشف الميزة عن استدعاءات lambda مثل 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 الخاص بها.
- تتواصل الميزات للتعبير عن النية من خلال استدعاءات lambda، لا من خلال التنقل المباشر.
- تعرف :app وحدها جميع الميزات، وتربط الاستدعاءات بالمسارات الفعلية.
- إذا وجب مشاركة مسار، فاعرضه من خلال وحدة :api صغيرة.
يحافظ ذلك على استقلالية الميزات، وإمكانية تخزين بنائها مؤقتًا، وخلوها من الحلقات.
تحقق سريع
في تطبيق متعدد الوحدات، تحتاج :feature:home إلى إرسال المستخدم إلى شاشة في :feature:profile. ما الطريقة الأنظف لإبقاء الميزتين منفصلتين؟
مراجعة: التنقل عبر الوحدات
لقد تعلمت كيفية ربط الميزات دون إحكام اقترانها:
- يوجد
NavHostالوحيد في وحدة :app النحيفة. - تملك كل ميزة مسارًا
@Serializableوامتدادًا لـNavGraphBuilder. - تكشف الميزات عن استدعاءات بدلًا من التنقل مباشرةً إلى ميزات أخرى.
- لا تشارك مسارًا إلا من خلال وحدة :api صغيرة عند الحاجة الفعلية؛ وتنطبق الروابط العميقة والرسوم البيانية المتداخلة على النمط نفسه.
وبذلك يكتمل درس بنية التطبيقات متعددة الوحدات: يمكنك الآن تقسيم قاعدة شيفرة Android القابلة للتوسع وربطها والتنقل فيها.
الأسئلة الشائعة
هل درس «التنقّل بين الوحدات» مجاني؟
نعم — نص درس «التنقّل بين الوحدات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Android Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Android Academy 4 دروس في المجموع.
ماذا ستتعلم في «التنقّل بين الوحدات»؟
صِل الميزات من دون ترابط وثيق تتمرن على Android Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Android Academy؟
لا تُشترط خبرة سابقة. Android Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «التنقّل بين الوحدات»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Android Academy هذا؟
نعم. كل درس في Android Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- لماذا نُجزّئ التطبيق إلى وحدات
- وحدات الميزات والوحدات الأساسية
- إدارة تبعيات الوحدات
- التنقّل بين الوحدات