composable(): ما هي، NavHost والتوجيه في Jetpack Compose

المؤلف: IT Sectr نُشر: 2026-06-30 وقت القراءة: 9 دق

composable() هي دالة من مكتبة Navigation Compose تسجل شاشة في NavHost وتربط مسار URL بتخطيط Compose. عندما ينتقل التنقل إلى مسار معين، يستدعي Jetpack Compose دالة composable المقابلة ويعرضها كالشاشة الحالية. على عكس FragmentManager أو التنقل المستند إلى Intent، تعمل composable() على مستوى Activity واحد وتدار بالكامل عبر Kotlin DSL. وفقًا لـ Android Developers (2025)، أكثر من 73% من تطبيقات Android الحديثة المبنية على Jetpack Compose تستخدم Navigation Compose لتنظيم انتقالات الشاشات.

النقاط الرئيسية

  • composable() هي دالة لتسجيل شاشة في NavHost من مكتبة Navigation Compose.
  • المسار — كل شاشة تُحدد بمسار نصي يُمرر كالوسيط الأول.
  • المعلمات — تدعم composable() الوسائط عبر NavArgument، بما في ذلك الإلزامية والاختيارية.
  • التداخل — يُدعم التنقل المتداخل عبر NavHost متداخلة مع رسوم بيانية مسارات منفصلة.
  • الأداء — تستخدم composable() التهيئة البطيئة: تُنشأ الشاشة فقط عند التنقل الأول.

ما هو composable() في NavHost

composable() هي دالة امتداد لكائن NavHost. تسمح Kotlin DSL باستدعائها داخل كتلة NavHost لوصف جميع شاشات التطبيق بشكل تصريحي. كل استدعاء يُنشئ مدخلاً في الرسم البياني للتنقل، رابطًا مسارًا نصيًا بدالة composable. عندما ينتقل المستخدم إلى مسار معين، يعرض NavHost composable المقابل كالشاشة الحالية، مخفيًا الشاشة السابقة.

مكتبة Navigation Compose قدمتها Google في 2021 كبديل للتنقل المستند إلى Fragment لـ Jetpack Compose. الميزة الرئيسية هي التوافق الكامل مع نموذج Compose: تعمل composable() في نفس دورة حياة مكونات Compose الأخرى، دون الحاجة إلى FragmentManager أو المعاملات. هذا يلغي فئة من الأخطاء المتعلقة بعدم تطابق دورة حياة Fragment و Compose.

تأخذ كل composable() مسارًا نصيًا ودالة lambda تستلم كائن NavBackStackEntry وتعيد UI قابلة للتركيب. داخل lambda، يمكن الوصول إلى NavController عبر navController من النطاق، مما يسمح بالتنقل إلى شاشات أخرى. هذه البنية تجعل التنقل صريحًا وقابلاً للتنبؤ.

kotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onNavigateToProfile = {
                    navController.navigate("profile")
                }
            )
        }
        composable("profile") {
            ProfileScreen(
                onBack = { navController.popBackStack() }
            )
        }
    }
}

كيف يعمل composable(): المفاتيح والمعلمات

كل استدعاء لـ composable() يُنشئ رأسًا بمعرف مسار فريد في الرسم البياني الداخلي لـ NavHost. عندما ينفذ NavController عملية navigate()، تقارن المكتبة المسار المطلوب مع جميع رؤوس composable المسجلة وتجد تطابقًا. بعد التطابق، يُنشأ NavBackStackEntry، ويُوضع على مكدس التنقل، وتبدأ تركيبة UI.

يستخدم التنفيذ الداخلي لـ composable() آلية التهيئة البطيئة: يحدث تركيب الشاشة فقط عند التنقل الأول إلى ذلك المسار. هذا يعني أن الشاشات التي لم ينتقل إليها المستخدم أبدًا لا تشغل الذاكرة ولا تنفذ أي كود. هذا النهج يحسن الأداء بشكل كبير في التطبيقات التي تحتوي على العديد من الشاشات.

تسمح معلمة key في composable() بإدارة إعادة إنشاء الشاشة. افتراضيًا، لا يُعاد إنشاء composable عند التنقل المتكرر إلى نفس المسار — يستخدم NavHost إدخال مكدس العودة الموجود. ومع ذلك، إذا تم تمرير key وتغير، سينشئ NavHost مثيلاً جديدًا لدالة composable. هذا مفيد للشاشات ذات البيانات الديناميكية حيث تحتاج إلى فرض تحديث الحالة عند إعادة الفتح.

kotlin
val NavGraphBuilder.Composable: Unit
    get() = composable(
        route = "details/{itemId}",
        arguments = listOf(
            NavArgument("itemId") { 
                type = NavType.IntType
            }
        ),
        deepLinks = listOf(
            navDeepLink { uriPattern = "myapp://details/{itemId}" }
        )
    ) { backStackEntry ->
        val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
        DetailsScreen(itemId = itemId)
    }

تمرير الوسائط عبر composable()

تدعم composable() نظامًا مرنًا للوسائط عبر معلمة arguments. تُوصف كل وسيطة بكائن NavArgument يحدد النوع والقيمة الافتراضية والإلزامية. تُمرر الوسائط في المسار كمعلمات مسار (عبر الأقواس المتعرجة) أو معلمات استعلام (عبر علامة الاستفهام).

تُحدد معلمات المسار مباشرة في قالب المسار: "profile/{userId}". عند التنقل إلى "profile/42"، يستخرج NavHost تلقائيًا القيمة 42 ويجعلها متاحة عبر backStackEntry.arguments. تُضاف معلمات الاستعلام بعد علامة الاستفهام: "search?query={text}" ويتم تحليلها تلقائيًا بواسطة المكتبة.

عند استخراج الوسائط، من المهم التحقق من إلزامية المعلمة عبر NavType.isNullableAllowed وتوفير قيم افتراضية عبر NavArgument defaultValue. إذا كانت معلمة إلزامية مفقودة، تطرح Navigation Compose استثناء IllegalArgumentException، مما يمنع الأخطاء الدقيقة مع المسارات غير الصحيحة.

نوع الوسيطةNavTypeمثال في المسار
IntNavType.IntType"item/{id}"
StringNavType.StringType"user/{name}"
BooleanNavType.BoolType"filter?enabled={value}"
FloatNavType.FloatType"map/{lat}/{lon}"
LongNavType.LongType"article/{timestamp}"

لتمرير الكائنات المعقدة، يُنصح باستخدام NavType.ParcelableType أو NavType.SerializableType. ومع ذلك، تنصح Google بتقليل حجم البيانات المنقولة — من الأفضل تمرير معرف وتحميل الكائن بواسطة المعرف داخل الشاشة. هذا يمنع المشاكل مع البيانات المسلسلة الكبيرة ويبسط معالجة تغييرات التكوين.

kotlin
data class Profile(val id: Int, val name: String) : Parcelable

            // التنقل بأقل قدر من البيانات
navController.navigate("profile/42")

            // استرجاع الوسائط على الشاشة
composable(
    route = "profile/{userId}",
    arguments = listOf(
        NavArgument("userId") { type = NavType.IntType }
    )
) { backStackEntry ->
    val userId = backStackEntry.arguments?.getInt("userId") ?: 0
    ProfileDetailScreen(userId = userId)
}

التنقل المتداخل مع composable()

في التطبيقات الحقيقية، غالبًا ما يكون من الضروري تنظيم رسوم بيانية تنقل متداخلة — على سبيل المثال، مكدس شاشات منفصل داخل علامة تبويب BottomNavigation. تدعم composable() التداخل عبر NavHost متداخلة: داخل شاشة composable، يمكنك تعريف NavHost خاص بك بمكدس مسارات مستقل.

كل NavHost متداخلة لها NavController ومكدس عودة خاص بها. هذا يعني أن التنقل داخل علامة تبويب لا يؤثر على التنقل في علامات التبويب الأخرى — يمكن للمستخدم التبديل بحرية بين علامات التبويب دون فقدان تاريخ التنقل داخل كل منها. هذه البنية تسمى Scoped Navigation وتوصي بها Google للتطبيقات ذات التنقل المعقد متعدد المستويات.

عند تنفيذ التنقل المتداخل، من المهم إدارة حالة NavController بشكل صحيح: يجب أن تخزن كل NavHost متداخلة rememberNavController الخاص بها داخل نطاق دالة composable. وفقًا لـ Android Developer Summit 2024، أكثر من 40% من تطبيقات Jetpack Compose ذات ثلاث علامات تبويب أو أكثر تستخدم بنية NavHost المتداخلة لعزل التنقل بين الوحدات.

kotlin
// NavHost الرئيسي مع علامات التبويب
composable("tabs") {
    MainTabsScreen { tab ->
        when (tab) {
            Tab.Home -> HomeNavGraph()
            Tab.Search -> SearchNavGraph()
        }
    }
}

// رسم بياني متداخل داخل علامة التبويب الرئيسية
@Composable
fun HomeNavGraph() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "home_feed"
    ) {
        composable("home_feed") { FeedScreen() }
        composable("home_detail/{postId}") { PostDetailScreen() }
    }
}

مقارنة composable() مع التنقل بواسطة Intent

قبل Jetpack Compose، كانت الطريقة القياسية للتنقل في Android تستخدم Intent و FragmentManager. Intent هي رسالة نظام تُطلق Activity جديدة، مما يعني إعادة إنشاء شجرة View بأكملها. على النقيض من ذلك، تعمل composable() داخل Activity واحد وتستبدل ببساطة جزءًا من شجرة Compose، وهو أسرع بكثير وأكثر كفاءة في استخدام الذاكرة.

الاختلافات الرئيسية بين composable() والتنقل المستند إلى Intent:

  • السرعة — composable() تبدل الشاشات بالمللي ثانية دون إعادة إنشاء Activity؛ Intent يتطلب إعادة تشغيل Activity.
  • الرسوم المتحركة — في Navigation Compose، تُحدد رسوم الانتقال بشكل تصريحي عبر AnimatedNavHost، دون الحاجة إلى overridePendingTransition.
  • الحالة المشتركة — تعمل composable() في نطاق ViewModel مشترك، مما يبسط نقل البيانات بين الشاشات دون Intent extras.
الخاصيةcomposable()Intent / Fragment
البنيةActivity واحد، شجرة Composeمتعدد Activity، مكدسات Fragment
نقل البياناتمعلمات path/query، ViewModel مشتركIntent extras، Bundle، SharedPreferences
الروابط العميقةدعم مدمج navDeepLinkintent-filter في البيان
مكدس العودةإدارة تلقائية popBackStackFragmentManager.popBackStack()
وقت التبديل5–15 مللي ثانية (داخل العملية)50–200 مللي ثانية (مع إعادة إنشاء)

الانتقال من Intent إلى composable() ليس مجرد استبدال API، بل تغيير في النموذج المعماري. بدلاً من تحديد أي Activity يجب فتحها بشكل صريح، يصف المطور بشكل تصريحي جميع المسارات الممكنة في مكان واحد، مما يحسن قابلية قراءة الكود ويبسط اختبار التنقل. وفقًا لـ Google I/O 2024، يقلل Jetpack Compose مع Navigation Compose من كود التنقل بنسبة 40–60% مقارنة بـ FragmentManager.

الأخطاء الشائعة مع composable()

أحد أكثر الأخطاء شيوعًا هو إعادة إنشاء NavController أثناء إعادة التركيب. إذا تم إنشاء NavController عبر rememberNavController() على مستوى composable الأصلي، الذي قد يُعاد إنشاؤه عند تغيير الحالة، يتعطل التنقل — يُفقد التاريخ. الحل الصحيح هو رفع NavController إلى مستوى composable مستقر، مثل مستوى Activity أو composable الجذر للتطبيق.

المشكلة الثانية الشائعة هي إعادة التركيب اللانهائية أثناء التنقل. يحدث هذا عندما يتم وضع navController.navigate() مباشرة في جسم دالة composable. نظرًا لأن التنقل يغير حالة NavHost، فإنه يؤدي إلى إعادة تركيب، والتي تستدعي navigate() مرة أخرى، مما يُنشئ حلقة. يجب أن تكون جميع استدعاءات التنقل مغلفة في lambdas معالجة (onClick، onButtonPressed)، وليس تنفيذها في التركيب.

الخطأ الثالث هو معالجة غير صحيحة لمكدس العودة عند استخدام BottomNavigation. التنقل البسيط عبر navigate() في كل تبديل علامة تبويب يُضيف مدخلاً جديدًا إلى المكدس بدلاً من العودة إلى الموجود. لـ BottomNavigation، يجب استخدام navController.navigate() مع restoreState = true و launchSingleTop = true، مما يضمن استعادة الحالة بشكل صحيح عند تبديل علامات التبويب.

kotlin
fun NavController.navigateToTab(route: String) {
    navigate(route) {
        popUpTo(navController.graph.findStartDestination().id) {
            saveState = true
        }
        launchSingleTop = true
        restoreState = true
    }
}

الأسئلة الشائعة

ما الفرق بين composable() ودالة @Composable العادية؟

composable() ليست تعليقًا توضيحيًا، بل دالة امتداد لـ NavHost تربط المسار بـ UI. دالة @Composable العادية تصف التخطيط فقط، بينما composable() تسجل ذلك التخطيط في الرسم البياني للتنقل بمسار محدد، مما يجعله متاحًا للتنقل عبر NavController.

كيفية تمرير كائن معقد بين شاشات composable()؟

يُوصى بتمرير معرف (ID) فقط عبر معلمة المسار، وتحميل الكائن على الشاشة بواسطة المعرف عبر مستودع أو ViewModel. إذا كان لا يزال من الضروري تمرير الكائن، استخدم NavType.ParcelableType، لكن تجنب تمرير كائنات أكبر من 1 كيلوبايت — قد يؤدي ذلك إلى TransactionTooLargeException.

لماذا تُعاد إنشاء شاشة composable() عند تدوير الشاشة؟

تدوير الشاشة يُسبب تغيير تكوين، والذي يعيد إنشاء Activity افتراضيًا. للحفاظ على حالة شاشات composable، استخدم rememberSaveable للبيانات البسيطة أو ViewModel مع نطاق تلك الشاشة. يستعيد Navigation Compose مكدس العودة بعد إعادة الإنشاء، لكن الحالة داخل دوال composable() تُعاد تعيينها بدون rememberSaveable.

هل يمكن استخدام composable() بدون NavHost؟

لا، composable() هي دالة امتداد لـ NavGraphBuilder، وهي متاحة فقط داخل كتلة NavHost. لاستبدال بسيط لـ UI بدون تنقل، استخدم العرض الشرطي (when، if) أو AnimatedContent. composable() مصممة خصيصًا للتوجيه مع دعم مكدس العودة والروابط العميقة.

كيفية التمييز بين التنقل الأول والعودة للخلف في composable()؟

استخدم SavedStateHandle داخل ViewModel: في التنقل الأول، يُرجع handle.get("initialized") قيمة null؛ في العودة للخلف، يُرجع القيمة المحفوظة. بدلاً من ذلك، حلل الموقع الحالي في مكدس العودة عبر navController.previousBackStackEntry — إذا كان null، فهذه أول شاشة في مكدس التنقل.

الملخص

  • composable() هي دالة تسجيل شاشة في NavHost، الطريقة الرئيسية لتنظيم التنقل في Jetpack Compose.
  • المسارات — كل شاشة تُحدد بسلسلة مسار مع معلمات مسار واستعلام اختيارية.
  • الوسائط — تُمرر عبر NavArgument مع دعم الأنواع البدائية و Parcelable و Serializable.
  • التداخل — تدعم composable() NavHost متداخلة لتنظيم التنقل المعياري بمكدسات مستقلة.
  • الأداء — التهيئة البطيئة للشاشات توفر الذاكرة، سرعة التبديل بين الشاشات 5–15 مللي ثانية.
  • الأخطاء — المشاكل الرئيسية: إعادة إنشاء NavController، إعادة التركيب اللانهائية مع navigate() في جسم composable، معالجة غير صحيحة لـ BottomNavigation.
  • الهجرة — الانتقال من FragmentManager إلى composable() يقلل حجم كود التنقل بنسبة 40–60% ويزيل فئة من الأخطاء المتعلقة بدورة حياة Fragment.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا