NavHost هو حاوية composable تعمل كنقطة دخول للرسم البياني للتنقل في Jetpack Compose. يربط NavController بمجموعة من المسارات ويعرض الشاشة الحالية بناءً على حالة مكدس الرجوع. وفقًا لـ Android Developers (2025)، NavHost هو مكون إلزامي لأي تطبيق Compose مع تنقل. داخل NavHost، يتم تسجيل مسارات composable مع وسائط اختيارية، وروابط عميقة، ورسوم متحركة. كل مسار هو دالة composable عادية تتلقى NavBackStackEntry مع بيانات الانتقال. يعالج NavHost تلقائيًا زر الرجوع، وحفظ الحالة، واستعادتها عند إعادة التهيئة.
الوجبات الرئيسية
NavHost هي دالة composable توفر حاوية لعرض شاشة التنقل الحالية. يستقبل NavHost NavController و startDestination ورسمًا بيانيًا للمسارات مبنيًا عبر Kotlin DSL. عندما يتغير المسار الحالي، يبدل NavHost composable المعروض بالرسوم المتحركة المحددة.
يعمل NavHost كـ مبدل شاشات: يتتبع NavBackStackEntry الحالي من NavController ويعرض كتلة composable المقابلة. كل شاشة هي دالة composable مستقلة تتلقى NavBackStackEntry مع وسائط المسار. توجد جميع الشاشات في شجرة تركيب واحدة، لكن NavHost يعرض واحدة فقط في كل مرة، مخفيًا الأخرى عبر الرسوم المتحركة.
على عكس FragmentManager، لا ينشئ NavHost Fragment لكل شاشة. تُدار دورة الحياة بالكامل عبر CompositionLifecycle — دوال composable لا تحتوي على onStart/onResume، لذلك تُستخدم LaunchedEffect و DisposableEffect للتأثيرات الجانبية. يشترك NavHost تلقائيًا في NavController ويعيد تركيب واجهة المستخدم عندما يتغير المسار.
وفقًا لـ Google، NavHost هو API مستقر منذ Navigation 2.4.0. بدءًا من 2.8.0، يدعم NavHost التنقل Type-Safe عبر Kotlin Serialization، مما يستبدل مسارات السلسلة بفئات بيانات. يدعم NavHost أيضًا الرسوم البيانية المتداخلة، مما يتيح تنظيمًا معياريًا للتنقل.
يُ创建 NavHost بمعاملين إلزاميين: navController (مثيل من NavHostController) و startDestination (سلسلة مسار الشاشة الأولى). المعامل الثالث هو كتلة بناء حيث تُسجل جميع المسارات عبر composable() و navigation() و dialog().
@Composable
fun AppNavHost(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") { HomeScreen(navController) }
composable("settings") { SettingsScreen(navController) }
}
}
startDestination هو المسار الذي يُفتح عند بدء تشغيل NavHost لأول مرة. إذا كانت مكدس الرجوع فارغًا، يضيف NavHost تلقائيًا startDestination إلى المكدس. عند إعادة التهيئة (تدوير الشاشة)، يستعيد NavHost آخر مسار من savedState، وليس startDestination.
بالنسبة لـ BottomNavigation، startDestination هو أحد مسارات اللوحة السفلية. تُضاف مسارات اللوحة المتبقية كإدخالات composable منفصلة. يجب وضع NavHost داخل Scaffold.content — حيث يُعرض المحتوى الرئيسي للتطبيق. يشغل NavHost كل الارتفاع المتاح مطروحًا منه TopAppBar و BottomNavigation.
تسجل دالة composable(route, arguments, deepLinks, enterTransition, exitTransition, content) مسارًا في رسم NavHost البياني. معامل route هو سلسلة تصف المسار مع عناصر نائبة اختيارية بالشكل {paramName}. يُستبدل العنصر النائب بقيمة فعلية أثناء التنقل.
تستقبل كتلة content الخاصة بـ composable NavBackStackEntry الذي تُستخرج منه الوسائط. تُعرض دالة composable للشاشة فقط عندما يتطابق مسار NavController الحالي مع المسار. في حال عدم التطابق، يُزال composable من التركيب، لكن يمكن الحفاظ على حالته عبر rememberSaveable أو ViewModel مع SavedStateHandle.
composable(
route = "article/{articleId}",
arguments = listOf(navArgument("articleId") {
type = NavType.IntType
defaultValue = 0
}),
deepLinks = listOf(navDeepLink { uriPattern = "https://app.example/article/{articleId}" })
) { backStackEntry ->
val articleId = backStackEntry.arguments?.getInt("articleId") ?: 0
ArticleScreen(articleId = articleId)
}
يمكن أن يتراوح عدد إدخالات composable داخل NavHost من بضع إلى مئات. للتطبيقات الكبيرة، تُقسّم المسارات عبر وحدات وتُوصّل عبر رسوم بيانية متداخلة. يمكن أن يكون لكل composable إعدادات الرسوم المتحركة والروابط العميقة والوسائط الخاصة به.
تُحدد وسائط المسار عبر معامل arguments: List<NamedNavArgument> في composable(). يُحدد كل وسيط عبر navArgument(name) { type; defaultValue }. يحدد NavType نوع الوسيط: StringType، IntType، LongType، FloatType، BoolType، ParcelableType و ReferenceType.
| معامل المسار | مثال المسار | NavType |
|---|---|---|
| المسار (path) | "user/{id}" | NavType.IntType |
| الاستعلام (query) | "search?q={query}" | NavType.StringType |
| اختياري | "details/{id}?tab={tab}" | StringType + defaultValue="" |
| Parcelable | "checkout/{order}" | NavType.ParcelableType |
تُستخرج الوسائط من NavBackStackEntry عبر arguments?.getInt("id"). للوسائط الإلزامية، يمكن حذف defaultValue — سيستخدم NavType القيمة null. للوسائط الاختيارية، يجب تعيين defaultValue، وإلا سيرمي التنقل استثناءً في حال فقدان المعامل.
منذ Navigation 2.8.0، يُوصى بالتنقل Type-Safe: حدد فئة مختومة أو فئة بيانات للمسارات مع Kotlin Serialization. بدلاً من مسار سلسلة، استخدم composable<RouteType> { backStackEntry -> }. يزيل هذا الأخطاء المطبعية في المسارات ويُ生成 تلقائيًا NavType للوسائط. للترحيل، أضف تبعية navigation-compose-typesafe ومكون Kotlin Serialization الإضافي.
nested graphs — آلية لتجميع المسارات داخل NavHost باستخدام دالة navigation(route, startDestination). الرسم البياني المتداخل له بادئة مسار خاصة به و startDestination، وجميع مساره يمكن الوصول إليها عبر البادئة. تُستخدم الرسوم البيانية المتداخلة للهندسة المعيارية، حيث يسجل كل وحدة ميزات رسمها البياني الفرعي الخاص.
فوائد الرسوم البيانية المتداخلة: عزل المسارات داخل الوحدة، مكدس رجوع موحد لمجموعة من الشاشات، والقدرة على التنقل عبر البادئة دون كشف الهيكل الداخلي. على سبيل المثال، الرسم البياني "auth" يحتوي على "auth/login" و "auth/register". التنقل ممكن إما عبر المسار الكامل أو عبر البادئة مع إعادة توجيه إلى startDestination.
NavHost(navController = navController, startDestination = "main") {
composable("main") { MainScreen(navController) }
navigation(
route = "auth",
startDestination = "auth/login"
) {
composable("auth/login") { LoginScreen(navController) }
composable("auth/register") { RegisterScreen(navController) }
}
}
تدعم الرسوم البيانية المتداخلة تمرير الوسائط على مستوى الرسم البياني: المعاملات المُعلنة في مسار الرسم البياني تُمرر إلى جميع المسارات الداخلية. لمسح رسم بياني متداخل، استخدم popBackStack(route) — سيزيل جميع الإدخالات الداخلية. ليس للرسوم البيانية المتداخلة حد عمق، لكن يُوصى بما لا يزيد عن 3 مستويات للقراءة.
يدعم NavHost الرسوم المتحركة للانتقالات بين مسارات composable عبر معاملات enterTransition و exitTransition و popEnterTransition و popExitTransition. تُضبط الرسوم المتحركة مرة واحدة لـ NavHost وتُطبق على جميع المسارات، أو بشكل فردي لكل composable. افتراضيًا، الرسوم المتحركة معطلة.
التكوين النموذجي: enterTransition = slideInHorizontally(initialOffsetX = { it }) — تنزلق الشاشة من اليمين; exitTransition = slideOutHorizontally(targetOffsetX = { -it }) — تنزلق الشاشة إلى اليسار. لرسوم pop المتحركة، تنعكس الاتجاهات: تنزلق الشاشة من اليسار وتنزلق إلى اليمين. لـ BottomNavigation، يُستخدم fadeIn/fadeOut بدون انزلاق.
NavHost(
navController = navController,
startDestination = "home",
enterTransition = { slideInHorizontally(initialOffsetX = { it }) + fadeIn() },
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) + fadeOut() },
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) + fadeIn() },
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) + fadeOut() }
) { /* composable routes */ }
تُنشأ الرسوم المتحركة المخصصة عبر Compose Animation API: يوفر AnimatedContentTransitionScope الوصول إلى أبعاد الحاوية وتقدم الرسم المتحرك والاتجاه. للانتقالات ذات العناصر المشتركة (عنصر ينتقل بسلاسة إلى شاشة أخرى)، يلزم مكتبة Accompanist Navigation Animation أو تطبيق مخصص عبر sharedElement Modifier. وفقًا لـ Android Developers (2025)، الرسم المتحرك الافتراضي للانزلاق (دخول من اليمين، خروج إلى اليسار) يُستخدم في 80% من تطبيقات Android مع التنقل.
الأسئلة الشائعة
نعم تقنيًا، لكن لا يُوصى بذلك. كل NavHost ينشئ مكدس رجوع مستقل، مما يكسر التنقل الموحد. الاستثناء هو مناطق منفصلة، مثل NavHost للمحتوى الرئيسي و NavHost لـ BottomSheet مع تنقل خاص به.
NavHost — حاوية تنقل تتبدل بين الشاشات. Scaffold — تخطيط الصفحة بأكملها (TopAppBar، BottomNavigation، FloatingActionButton). عادةً، يُوضع NavHost داخل Scaffold.content. Scaffold لا يُدير التنقل، بل يوفر فقط فتحات لمكونات واجهة المستخدم.
يُ创建 ViewModel ضمن NavBackStackEntry عبر viewModel(). لمشاركة ViewModel بين الشاشات، استخدم parentNavController: اربط ViewModel المشترك بالإدخال الأب. البديل هو DI (Hilt/Koin) مع نطاق NavGraph.
هذا سلوك طبيعي — يزيل NavHost composable من التركيب عند مغادرة مسار. للحفاظ على الحالة، استخدم rememberSaveable لحالة واجهة المستخدم و ViewModel مع SavedStateHandle لمنطق الأعمال.
أضف مسارًا نهائيًا composable("404") وتنقل إليه عند استقبال رابط عميق غير معروف. NavHost لا يحتوي على مسار catch-all — تحقق من المسار في معالج intent للرابط العميق قبل navigate(). إذا لم يُعثر على المسار، تنقل إلى 404.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.