NavController مؤلفه مرکزی کتابخانه Navigation Compose است که پیمایش پشته و وضعیت back stack را در برنامههای Android مدیریت میکند. از طریق NavController انتقال بین صفحهها، بازگشت به صفحات قبلی و انتقال داده بین مسیرها انجام میشود. به گفته Android Developers (2025)، NavController یک عنصر اجباری برای هر برنامه Compose با بیش از یک صفحه است. کنترلر از طریق rememberNavController() ایجاد میشود، به NavHost منتقل میشود و برای فراخوانی navigate() از هر نقطه ترکیب در دسترس است. پشتیبانی داخلی SavedStateHandle به طور خودکار وضعیت ViewModel را هنگام بازپیکربندی ذخیره میکند.
نکات اصلی
NavController — کلاسی از کتابخانه Navigation Compose است که کنترلر ناوبری را برای برنامههای Compose پیادهسازی میکند. NavController پشته NavBackStackEntry را مدیریت میکند، جایی که هر ورودی شامل مسیر، آرگومانها و وضعیت صفحه است. کنترلر از عملیات اصلی ناوبری پشتیبانی میکند: انتقال، بازگشت، جایگزینی و پاکسازی.
برخلاف سیستم View که در آن ناوبری از طریق FragmentManager یا Intent انجام میشد، NavController منحصراً در زمینه Compose کار میکند. back stack به صورت گراف NavDestination ذخیره میشود، نه پشته Fragment. این کار سربار ایجاد و نابودی Fragment را حذف میکند و همچنین آزمایش را ساده میکند — NavController را میتوان از طریق TestNavHostController شبیهسازی کرد.
NavController ارتباط نزدیکی با NavHost دارد — ظرفی که صفحه فعلی را از گراف رندر میکند. بدون NavHost، NavController نمیتواند توابع composable را نمایش دهد، اما توانایی مدیریت پشته را حفظ میکند. در معماری معمولی، NavController در سطح Activity یا composable اصلی ایجاد میشود و از طریق پارامترها به پایین درخت ترکیب منتقل میشود.
به گفته Google، NavController چندین انتشار اصلی داشته است. نسخه 2.8.0 ناوبری Type-Safe را اضافه کرد، نسخه 2.9.0 — پشتیبانی از predictive back gesture (Android 14+). کنترلر با Material3 Scaffold و BottomNavigation سازگار است. برای پروژههای چندماژولی، NavController از طریق DI (Hilt/Koin) یا پارامترهای سازنده منتقل میشود.
NavController از طریق تابع composable rememberNavController() ایجاد میشود. تابع یک نمونه NavHostController (وارث NavController) مرتبط با چرخه حیات composable فعلی برمیگرداند. هنگام خروج از ترکیب، کنترلر پاک میشود. برای ذخیره کنترلر در بازپیکربندی، از rememberSaveable یا ViewModel استفاده کنید.
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "main"
) {
composable("main") { MainScreen(navController) }
composable("details") { DetailsScreen(navController) }
}
}
پیکربندی NavController شامل است: NavHostController (اصلی)، TestNavHostController (آزمایش) و ScopedNavController (فرعی برای گرافهای تو در تو). برای BottomNavigation، NavController باید برای کل برنامه واحد باشد — ایجاد کنترلر جدید در هر زبانه منجر به از دست رفتن پشته میشود. برای انتقال کنترلر به صفحات تو در تو، از پارامتر تابع استفاده کنید، نه CompositionLocalProvider، تا خوانایی حفظ شود.
برای آزمایش ناوبری، از TestNavHostController با compose-test-rule استفاده کنید. کنترلر امکان تنظیم مسیر اولیه و بررسی اینکه navigate() انتقال مورد انتظار را فراخوانی کرده است. آزمایش NavController به شبیهساز نیاز ندارد — با Semantics-matcherهای Compose Test کار میکند.
روش navigate(route: String) — روش اصلی ناوبری در NavController است. یک رشته مسیر، NavOptions اختیاری و Navigator.Extras را دریافت میکند. NavOptions رفتار انتقال را مدیریت میکند: launchSingleTop (مسیر را در پشته تکرار نکن)، popUpTo (پشته را تا مسیر پاک کن)، restoreState (وضعیت قبلی را بازیابی کن).
NavOptions از طریق نحو builder تنظیم میشوند: NavOptionsBuilder. پارامترهای اصلی: popUpTo (route + inclusive/saveState)، launchSingleTop (Boolean، true — تکرار ایجاد نکن)، restoreState (وضعیت را در بازگشت بازیابی کن). بدون popUpTo، هر navigate() یک ورودی به پشته اضافه میکند که منجر به انباشت back stack و رفتار نادرست دکمه Back میشود.
navController.navigate("profile/42") {
popUpTo("main") { saveState = true }
launchSingleTop = true
restoreState = true
}
Navigator.Extras امکان انتقال دادههای اضافی که بخشی از مسیر نیستند را فراهم میکند: shared element برای انیمیشن، پرچمهای Intent، Pac-Man bundle. Extras به ندرت استفاده میشود — عمدتاً برای ادغام با Accompanist Animation یا Navigator سفارشی. برای اکثر سناریوها، رشته مسیر و NavOptions کافی است.
popBackStack() — روش بازگشت به صفحه قبلی. بدون آرگومان ورودی بالای پشته را حذف میکند و در صورت موفقیت true برمیگرداند. اگر پشته خالی باشد — روش false برمیگرداند و Activity بسته میشود (مشابه super.onBackPressed()).
نسخه بارگیری شده popBackStack(route: String, inclusive: Boolean) تمام ورودیها را تا مسیر مشخص حذف میکند. اگر inclusive = true باشد — خود مسیر مشخص نیز حذف میشود. روش Boolean برمیگرداند — true اگر ورودیها پیدا و حذف شوند. نسخه با inclusive برای سناریوهای «خروج به صفحه اصلی» پس از مجوز یا تکمیل سفارش مفید است.
| روش | شرح | مثال |
|---|---|---|
| popBackStack() | بازگشت یک صفحه به عقب | navController.popBackStack() |
| popBackStack(route, false) | پاکسازی تا مسیر (مسیر باقی میماند) | popBackStack("home", false) |
| popBackStack(route, true) | پاکسازی تا و شامل مسیر | popBackStack("home", true) |
| navigate(route) { popUpTo(route) { inclusive = true } } | انتقال با پاکسازی کامل | navigate("login") { popUpTo(0) { inclusive = true } } |
برای مدیریت دکمه Back سیستمی (hardware back button)، از BackHandler از Compose استفاده کنید. BackHandler enabled و onBack — callback فراخوانی شده در فشار را دریافت میکند. برای Android 14+ از PredictiveBackGesture استفاده میشود که از طریق NavController نسخه 2.9.0 ادغام شده است. Predictive back یک انیمیشن پیشنمایش بازگشت اضافه میکند.
SavedStateHandle — مکانیزمی برای ذخیره وضعیت ViewModel هنگام ناوبری و بازپیکربندی است. NavController به طور خودکار SavedStateHandle را برای هر NavBackStackEntry فراهم میکند. از طریق SavedStateHandle، ViewModel وضعیت صفحه را ذخیره میکند و در بازگشت آن را بازیابی میکند (restoreState = true).
در Navigation Compose، SavedStateHandle همراه با ViewModel استفاده میشود: ViewModel از طریق SavedStateHandle که از backStackEntry منتقل میشود مقداردهی اولیه میشود. هنگام انتقال به صفحه دیگر و بازگشت (با restoreState)، ViewModel وضعیت ذخیره شده را دریافت میکند، دوباره ایجاد نمیشود. این برای صفحات با ورود داده، فیلترها یا پیمایش حیاتی است.
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userId: String = savedStateHandle.get<String>("userId") ?: ""
var searchQuery by savedStateHandle.getStateFlow("search", "")
.collectAsState()
}
SavedStateHandle از انواع اولیه، String، Bundle و Parcelable پشتیبانی میکند. برای اشیاء پیچیده، فقط ID را ذخیره کنید و دادههای کامل را از مخزن بارگیری کنید. محدودیت SavedStateHandle حدود 1 MB است، تجاوز از آن باعث TransactionTooLargeException میشود. برای حجمهای بزرگ، به جای ذخیره در handle از Room یا DataStore استفاده کنید.
مهم: SavedStateHandle فقط هنگام استفاده از restoreState = true در NavOptions وضعیت را ذخیره میکند. اگر restoreState مشخص نشده باشد، در بازگشت ViewModel با مقادیر پیشفرض دوباره ایجاد میشود. برای تغییر BottomNavigation با restoreState، NavController وضعیت هر زبانه را ذخیره میکند و در انتخاب مجدد بازیابی میکند.
currentBackStackEntryAsState() — تابعی که State<NavBackStackEntry?> را برمیگرداند که با هر تغییر مسیر فعلی بهروز میشود. این مکانیزم اصلی همگامسازی UI با ناوبری است: BottomNavigation عنصر فعال را برجسته میکند، Toolbar عنوان را بهروز میکند، Drawer در انتقال بسته میشود.
تابع از طریق snapshotFlow و collectAsState کار میکند: هنگام تغییر back stack، Compose عناصر مشترک شده را دوباره ترکیب میکند. مهم: currentBackStackEntryAsState() فقط پس از اتمام انیمیشن انتقال بهروز میشود. برای بهروزرسانی فوری، از currentDestination استفاده کنید که با navigate() به طور همزمان تغییر میکند اما از وضعیت پشتیبانی نمیکند.
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Text(
text = when (currentRoute) {
"home" -> "Home"
"profile" -> "Profile"
else -> ""
}
)
برای دسترسی به آرگومانهای مسیر فعلی، از navBackStackEntry?.arguments استفاده کنید. این در BottomNavigation راحت است: selectedItem بر اساس currentRoute محاسبه میشود. برای اشکالزدایی ناوبری، از NavController.addOnDestinationChangedListener() استفاده کنید که هر انتقال را ثبت میکند. در production، از اشتراکها در داخل تعداد زیادی composable خودداری کنید — یک منبع واحد در ViewModel ایجاد کنید و State را به UI منتقل کنید.
سوالات متداول
از نظر فنی بله، اما توصیه نمیشود. یک NavController واحد back stack منسجم را تضمین میکند و اشکالزدایی را ساده میکند. چندین کنترلر فقط برای گرافهای تو در تو با ناوبری جداگانه موجه است (مثلاً، modal bottom sheet با پشته خود).
NavController را از طریق سازنده یا DI به ViewModel منتقل کنید. اما بهتر است فقط توابع callback (onNavigate، onBack) را منتقل کنید، نه خود NavController را — این آزمایش را ساده میکند. برای رویدادها، از Channel<NavEvent> در ViewModel استفاده کنید و در UI جمعآوری کنید.
مشکل در چرخه حیات است: اگر NavController هنوز مقداردهی اولیه نشده باشد (NavHost ساخته نشده)، navigate() نادیده گرفته میشود. از LaunchedEffect برای فراخوانی ناوبری پس از بارگیری داده استفاده کنید، نه داخل کوروتین با lifecycle دلخواه.
navController.navigate("target") { popUpTo(0) { inclusive = true } } را فراخوانی کنید. پارامتر popUpTo(0) پشته را کاملاً پاک میکند، inclusive = true ورودی اولیه را نیز حذف میکند. پرچم launchSingleTop = true از تکرار مسیر جدید جلوگیری میکند.
NavHostController — وارث NavController با روشهای اضافی برای NavHost (مثلاً setOnBackStackChangedListener). NavController — کلاس پایه که میتوان خارج از NavHost برای مدیریت برنامهای پشته استفاده کرد. در اکثر موارد، از NavHostController استفاده میشود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید