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)، بیش از ۷۳٪ از برنامه‌های مدرن Android ساخته شده با Jetpack Compose دقیقاً از Navigation Compose برای سازماندهی تغییر صفحات استفاده می‌کنند.

نکات اصلی

  • composable() — تابع ثبت صفحه در NavHost کتابخانه Navigation Compose.
  • مسیر — هر صفحه با یک مسیر متنی که به عنوان اولین آرگومان منتقل می‌شود شناسایی می‌گردد.
  • پارامترها — composable() از آرگومان‌ها از طریق NavArgument از جمله اجباری و اختیاری پشتیبانی می‌کند.
  • تودرتو — ناوبری تودرتو از طریق NavHostهای تودرتو با گراف‌های مسیر جداگانه پشتیبانی می‌شود.
  • کارایی — composable() از مقداردهی تنبل استفاده می‌کند: صفحه فقط در اولین انتقال ایجاد می‌شود.

composable() در NavHost چیست

composable() — تابع توسعه‌دهنده (extension function) شیء NavHost است. Kotlin DSL اجازه می‌دهد آن را در داخل بلوک NavHost برای توصیف اعلانی همه صفحات برنامه فراخوانی کنید. هر فراخوانی یک ورودی در گراف ناوبری ایجاد می‌کند و مسیر متنی را به تابع composable متصل می‌سازد. وقتی کاربر به مسیر مشخصی می‌رود، NavHost composable مربوطه را به عنوان صفحه فعلی نمایش می‌دهد و صفحه قبلی را پنهان می‌کند.

کتابخانه Navigation Compose توسط Google در سال ۲۰۲۱ به عنوان جایگزینی برای ناوبری مبتنی بر Fragment برای Jetpack Compose معرفی شد. مزیت اصلی — سازگاری کامل با پارادایم Compose: composable() در همان چرخه حیات سایر کامپوننت‌های Compose کار می‌کند، بدون نیاز به FragmentManager یا تراکنش‌ها. این کار دسته‌ای از خطاهای مربوط به ناسازگاری چرخه حیات Fragment و Compose را از بین می‌برد.

هر composable() یک مسیر متنی (route) و یک تابع lambda دریافت می‌کند که شیء NavBackStackEntry را گرفته و UI کامپوزable را برمی‌گرداند. در داخل 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 توصیف می‌شود که نوع، مقدار پیش‌فرض و اجباری بودن را تعیین می‌کند. آرگومان‌ها در مسیر به عنوان پارامترهای مسیر (از طریق آکولاد) یا پارامترهای query (از طریق علامت سؤال) منتقل می‌شوند.

پارامترهای مسیر مستقیماً در الگوی مسیر مشخص می‌شوند: "profile/{userId}". هنگام انتقال به مسیر "profile/42"، NavHost به طور خودکار مقدار ۴۲ را استخراج کرده و آن را از طریق backStackEntry.arguments در دسترس قرار می‌دهد. پارامترهای query بعد از علامت سؤال اضافه می‌شوند: "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 و back stack مخصوص خود است. این بدان معناست که ناوبری داخل یک تب روی ناوبری در ت‌ب‌های دیگر تأثیر نمی‌گذارد — کاربر می‌تواند آزادانه بین ت‌ب‌ها جابجا شود بدون اینکه تاریخچه انتقالات داخل هر کدام را از دست بدهد. چنین معماری Scoped Navigation نامیده می‌شود و توسط Google برای برنامه‌های با ناوبری پیچیده چندسطحی توصیه می‌شود.

در پیاده‌سازی ناوبری تودرتو، مدیریت صحیح وضعیت NavController مهم است: هر NavHost تودرتو باید rememberNavController خود را در محدوده تابع composable ذخیره کند. طبق داده‌های Android Developer Summit 2024، بیش از ۴۰٪ از برنامه‌های Jetpack Compose با سه ت‌ب و بیشتر از معماری NavHostهای تودرتو برای ایزوله کردن ناوبری بین ماژول‌ها استفاده می‌کنند.

kotlin
// NavHost اصلی با تب‌ها
composable("tabs") {
    MainTabsScreen { tab ->
        when (tab) {
            Tab.Home -> HomeNavGraph()
            Tab.Search -> SearchNavGraph()
        }
    }
}

// گراف تودرتو داخل تب Home
@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
معماریSingle Activity، درخت ComposeMulti Activity، پشته‌های Fragment
انتقال دادهپارامترهای مسیر/query، ViewModel مشترکIntent extras، Bundle، SharedPreferences
لینک‌های عمیقپشتیبانی داخلی navDeepLinkintent-filter در manifest
پشته بازگشتمدیریت خودکار popBackStackFragmentManager.popBackStack()
زمان جابجایی۵–۱۵ ms (داخل فرآیند)۵۰–۲۰۰ ms (با بازآفرینی)

انتقال از Intent به composable() — این فقط تغییر API نیست، بلکه تغییر پارادایم معماری است. به جای مشخص کردن صریح اینکه کدام Activity باید باز شود، توسعه‌دهنده همه مسیرهای ممکن را در یک مکان به صورت اعلانی توصیف می‌کند که خوانایی کد را بهبود می‌بخشد و آزمایش ناوبری را ساده می‌کند. طبق داده‌های Google I/O 2024، Jetpack Compose با Navigation Compose حجم کد ناوبری را ۴۰–۶۰٪ در مقایسه با FragmentManager کاهش می‌دهد.

خطاهای رایج با composable()

یکی از رایج‌ترین خطاها — بازآفرینی NavController در هنگام بازترکیب. اگر NavController از طریق rememberNavController() در سطح composable والد که می‌تواند با تغییر وضعیت بازآفرینی شود ایجاد گردد، ناوبری خراب می‌شود — تاریخچه انتقالات از دست می‌رود. راه‌حل صحیح بالا بردن NavController به سطح composable پایدار است، مثلاً به سطح Activity یا composable ریشه برنامه.

دومین مشکل رایج — بازترکیب بی‌نهایت هنگام ناوبری. این زمانی رخ می‌دهد که فراخوانی navController.navigate() مستقیماً در بدنه تابع composable قرار گرفته باشد. از آنجا که ناوبری وضعیت NavHost را تغییر می‌دهد، این باعث بازترکیب می‌شود که دوباره navigate() را فراخوانی کرده و یک چرخه ایجاد می‌کند. همه فراخوانی‌های ناوبری باید درون lambdaهای handler (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() — یک annotation نیست، بلکه تابع توسعه‌دهنده NavHost است که مسیر را به UI متصل می‌کند. تابع معمولی @Composable فقط طرح را توصیف می‌کند، در حالی که composable() این طرح را در گراف ناوبری با مسیر مشخص شده ثبت کرده و آن را برای ناوبری از طریق NavController قابل دسترس می‌سازد.

چگونه یک شیء پیچیده بین صفحات composable() منتقل کنیم؟

توصیه می‌شود فقط شناسه (ID) را از طریق پارامتر مسیر منتقل کرده و خود شیء را در صفحه با ID از طریق repository یا ViewModel بارگذاری کنید. اگر شیء باید منتقل شود، از NavType.ParcelableType استفاده کنید، اما از انتقال اشیاء بزرگتر از ۱ KB خودداری کنید — این می‌تواند به TransactionTooLargeException منجر شود.

چرا صفحه composable() هنگام چرخش صفحه بازآفرینی می‌شود؟

چرخش صفحه باعث تغییر پیکربندی می‌شود که به طور پیش‌فرض Activity را بازآفرینی می‌کند. برای حفظ وضعیت صفحات composable، از rememberSaveable برای داده‌های ساده یا ViewModel با محدوده آن صفحه استفاده کنید. Navigation Compose پس از بازآفرینی back stack را بازیابی می‌کند، اما وضعیت داخل توابع composable() بدون rememberSaveable بازنشانی می‌شود.

آیا می‌توان از composable() بدون NavHost استفاده کرد؟

خیر، composable() — تابع توسعه‌دهنده NavGraphBuilder است که فقط در داخل بلوک NavHost قابل دسترس است. برای جایگزینی ساده بخشی از UI بدون ناوبری از رندرینگ شرطی (when, if) یا AnimatedContent استفاده کنید. composable() دقیقاً برای مسیریابی با پشتیبانی از back stack و لینک‌های عمیق طراحی شده است.

چگونه اولین انتقال را از بازگشت در composable() تشخیص دهیم؟

از SavedStateHandle در داخل ViewModel استفاده کنید: در اولین انتقال handle.get("initialized") null برمی‌گرداند، در بازگشت — مقدار ذخیره‌شده. جایگزین، موقعیت فعلی در back stack را از طریق navController.previousBackStackEntry تحلیل کنید — اگر null باشد، این اولین صفحه در پشته ناوبری است.

خلاصه

  • composable() — تابع ثبت صفحه در NavHost، روش اصلی سازماندهی ناوبری در Jetpack Compose.
  • مسیرها — هر صفحه با یک رشته مسیر با پارامترهای مسیر و query اختیاری شناسایی می‌شود.
  • آرگومان‌ها — از طریق NavArgument با پشتیبانی از انواع primitives، Parcelable و Serializable منتقل می‌شوند.
  • تودرتو — composable() از NavHostهای تودرتو برای سازماندهی ناوبری ماژولار با پشته‌های مستقل پشتیبانی می‌کند.
  • کارایی — مقداردهی تنبل صفحات باعث صرفه‌جویی در حافظه می‌شود، زمان جابجایی بین صفحات ۵–۱۵ ms است.
  • خطاها — مشکلات اصلی: بازآفرینی NavController، بازترکیب بی‌نهایت هنگام فراخوانی navigate() در بدنه composable، عملکرد نادرست BottomNavigation.
  • مهاجرت — انتقال از FragmentManager به composable() حجم کد ناوبری را ۴۰–۶۰٪ کاهش می‌دهد و دسته‌ای از خطاهای مربوط به چرخه حیات Fragment را حذف می‌کند.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید