Deferred Navigation یک الگوی ناوبری معوق است که در آن انتقال به صفحه بعدی پس از اتمام عملیات ناهمگام انجام میشود، نه مستقیماً در لحظه اقدام کاربر. به گفته Android Developers (2024)، deferred navigation از شرط مسابقه بین ناوبری و بارگذاری داده جلوگیری میکند و همچنین پردازش انتقالهای اعلانهای فشاری و Deeplink را ساده میکند. تفاوت کلیدی — مسیر پس از در دسترس بودن تمام دادههای لازم محاسبه میشود.
نکات اصلی
Deferred Navigation یک الگوی معماری است که در آن تصمیم ناوبری تا زمانی که تمام دادههای لازم در دسترس قرار گیرند به تعویق میافتد. برخلاف انتقال مستقیم، جایی که کاربر دکمه را فشار میدهد و بلافاصله وارد صفحه جدید میشود، deferred navigation رویداد محرک و انتقال واقعی را جدا میکند و یک عملیات ناهمگام بین آنها اضافه میکند.
از نظر معماری، Deferred Navigation بر تغییر حالت استوار است: فشار دادن دکمه یک فرآیند ناهمگام را آغاز میکند و اشتراک در نتیجه آن، ناوبری را راهاندازی میکند. این امر به ویژه در برنامههایی با معماری MVVM یا MVI مهم است، جایی که ViewModel حالت را مدیریت میکند و View (Activity، Fragment، SwiftUI View) در تغییرات مشترک میشود و با انتقال واکنش نشان میدهد. چنین رویکردی وابستگی مستقیم بین UI و منطق ناوبری را از بین میبرد.
بر اساس دادههای Google I/O 2023، deferred navigation برای تمام سناریوهایی که ناوبری به نتیجه درخواست شبکه، بررسی احراز هویت، بارگذاری پیکربندی یا مجوزها بستگی دارد توصیه میشود. این الگو همچنین هنگام پردازش Deeplink الزامی است، زمانی که برنامه باید ابتدا راهاندازی شود، صفحه ریشه را بارگذاری کند و سپس به مسیر هدف منتقل شود.
Deferred Navigation در سناریوهایی استفاده میشود که انتقال مستقیم منجر به وضعیت نادرست صفحه یا خطاهای بارگذاری میشود. چهار مورد اصلی را در نظر میگیریم که deferred navigation الزامی است.
اگر کاربر روی محتوای محافظتشده کلیک کند، برنامه باید ابتدا توکن دسترسی را بررسی کند. ناوبری مستقیم به صفحه محتوا منجر به صفحه خالی یا خطای ۴۰۱ در صورت منقضی شدن توکن میشود. Deferred navigation توکن را بررسی میکند و فقط در صورت موفقیت به صفحه هدف منتقل میشود. در صورت ناموفق بودن به صفحه ورود هدایت میکند.
وقتی برنامه از طریق لینک خارجی باز میشود، ابتدا باید صفحه ریشه بارگذاری شود، وضعیت ناوبری بازیابی شود و سپس انتقال از طریق Deeplink انجام شود. ناوبری مستقیم به صفحه هدف بدون زمینه ریشه منجر به ناهنجاریهایی میشود: پشته ناوبری خالی یا back stack خراب.
با ضربه زدن روی اعلان فشاری، برنامه میتواند در سه حالت باشد: بسته، پسزمینه یا فعال. Deferred Navigation وضعیت برنامه را تعیین میکند، محتوای لازم را بارگذاری میکند و سپس صفحه هدف را نمایش میدهد. iOS این سناریو را از طریق UNNotificationContentExtension پردازش میکند.
اگر عملکرد صفحه توسط feature flag از سرور کنترل میشود، deferred navigation ابتدا پیکربندی را درخواست کرده و سپس صفحه را نمایش میدهد. اگر ویژگی غیرفعال باشد، کاربر به جای صفحه خالی محتوای جایگزین یا placeholder میبیند.
| سناریو | ناوبری مستقیم | Deferred Navigation |
|---|---|---|
| احراز هویت | صفحه خالی با توکن منقضی | هدایت به ورود |
| Deeplink | Back stack خراب | پشته ناوبری صحیح |
| اعلان فشاری | بارگذاری بدون زمینه | داده قبل از انتقال آماده است |
| Feature flag | نمایش عملکرد غیرقابل دسترس | Placeholder یا جایگزین |
ناوبری مستقیم (direct navigation) یک رویکرد سنتی است که در آن انتقال بلافاصله در پاسخ به رویداد انجام میشود. کاربر دکمه را فشار میدهد و مسیریاب UI فوراً صفحه را تغییر میدهد. این رویکرد ساده و قابل پیشبینی است، اما در سناریوهایی که نیاز به داده از سرور یا بررسی شرایط است محدود میشود.
Deferred Navigation یک لایه به شکل حالت ناهمگام اضافه میکند. رویداد کاربر عملیات را شروع میکند و اشتراک در نتیجه، ناوبری را مدیریت میکند. این پیچیدگی کد را افزایش میدهد اما انعطافپذیری میدهد: یک محرک واحد میتواند بسته به دادههای بارگذاریشده به صفحات مختلف منجر شود.
انتخاب بین دو رویکرد به نیازها بستگی دارد: اگر برای نمایش صفحه به داده ناهمگام نیاز نیست از ناوبری مستقیم استفاده کنید. اگر صفحه به نتیجه درخواست، احراز هویت یا شرایط خارجی وابسته است، deferred navigation الزامی است. رویکرد ترکیبی، جایی که بخشی از انتقالها مستقیم و بخشی معوق هستند، رایجترین روش در برنامههای صنعتی است.
Android Jetpack مکانیسمهایی برای پیادهسازی Deferred Navigation در سطح معماری فراهم میکند. ایده اصلی این است که ViewModel حالت را مدیریت میکند و Activity یا Fragment در تغییرات مشترک شده و از طریق NavController ناوبری را راهاندازی میکند.
StateFlow در Kotlin coroutines ابزاری ایدهآل برای deferred navigation است. ViewModel StateFlow را با رویداد ناوبری بهروز میکند و Activity آن را مشاهده کرده و انتقال را انجام میدهد. وقتی رویداد پردازش شد StateFlow پاک میشود و از ناوبری مجدد جلوگیری میکند.
class MainViewModel : ViewModel() {
private val _navigation = MutableSharedFlow<NavigationEvent>()
val navigation: SharedFlow<NavigationEvent> = _navigation
fun onDeepLinkReceived(link: String) {
viewModelScope.launch {
val data = resolveDeepLink(link)
_navigation.emit(NavigationEvent.GoToScreen(data))
}
}
}
در Activity اشتراک در ناوبری، NavController را با مسیر ViewModel راهاندازی میکند. برای جلوگیری از ناوبری مجدد هنگام چرخش صفحه، از NavigationEventWrapper استفاده میشود که رویداد را فقط یک بار پردازش میکند. Jetpack Navigation 2.7+ از Safe Args برای انتقال امن آرگومانها پشتیبانی میکند.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vm: MainViewModel by viewModels()
repeatOnLifecycle(Lifecycle.State.STARTED) {
vm.navigation.collect { event ->
when (event) {
is NavigationEvent.GoToScreen ->
findNavController(R.id.nav_host)
.navigate(event.route)
}
}
}
}
}
iOS Navigation Component داخلی مشابه Android Jetpack ندارد، بنابراین توسعهدهندگان Deferred Navigation را از طریق Coordinator Pattern همراه با Combine یا async/await پیادهسازی میکنند. Coordinator پشته صفحات را مدیریت میکند و بر اساس دادههای بارگذاریشده درباره ناوبری تصمیم میگیرد.
Coordinator شیئی است که ناوبری بین ViewControllerها را مدیریت میکند. در ترکیب با Combine، ViewModel رویدادها را از طریق PassthroughSubject منتشر میکند و Coordinator در آنها مشترک شده و انتقال را انجام میدهد. این رویکرد UI را کاملاً از منطق ناوبری جدا میکند و با توصیههای Apple برای معماری برنامه مطابقت دارد.
final class AppCoordinator {
private var cancellables = Set<AnyCancellable>()
func start(viewModel: MainViewModel) {
viewModel.$navigationDestination
.compactMap { $0 }
.sink { [weak self] destination in
self?.navigateTo(destination)
}
.store(in: &cancellables)
}
}
Swift 5.5 structured concurrency را معرفی کرد که امکان پیادهسازی ناوبری معوق را از طریق async/await بدون Combine فراهم میکند. ViewModel یک تابع async ارائه میدهد که پس از بارگذاری داده مسیر را برمیگرداند. Coordinator این تابع را در Task فراخوانی کرده و انتقال را طبق مسیر دریافتی انجام میدهد.
class AuthViewModel: ObservableObject {
func resolveDeeplink(_ url: URL) async -> AppRoute? {
guard let token = await AuthService.shared.getValidToken() else { return .login }
return await DeeplinkRouter.resolve(url, token: token)
}
}
// در Coordinator:
Task {
if let route = await viewModel.resolveDeeplink(url) {
navigateTo(route)
}
}
Deferred Navigation پردازش سناریوهای ناهمگام را ساده میکند اما نیاز به رویکرد منظمی برای مدیریت حالت دارد. بیایید خطاهای اصلی توسعهدهندگان هنگام پیادهسازی deferred navigation را بررسی کنیم.
رایجترین خطا تلاش برای انجام deferred navigation قبل از اینکه صفحه ریشه کاملاً مقداردهی شود و NavController یا Coordinator برای انتقال آماده باشد. در Android این منجر به IllegalStateException و در iOS به وضعیت نامشخص UI میشود. راه حل: مطمئن شوید چرخه حیات کامپوننت قبل از شروع ناوبری در حالت STARTED یا RESUMED است.
اگر StateFlow یا Subject رویداد را پس از پردازش پاک نکند، هنگام بازگشت به صفحه قبلی کاربر ممکن است به طور خودکار دوباره به همان صفحه هدایت شود. از SharedFlow با replay=0 در Android یا CurrentValueSubject با nil پس از پردازش در iOS استفاده کنید تا رویداد ناوبری فقط یک بار رخ دهد.
از یک کامپوننت مرکزی برای کل ناوبری در برنامه استفاده کنید. وقتی هر Activity، Fragment یا ViewController کنترلکننده ناوبری خود را دارد، deferred navigation بین بخشهای مختلف برنامه آشفته میشود. یک Coordinator واحد اشکالزدایی و آزمایش سناریوهای ناوبری را آسانتر میکند.
Deferred Navigation نسبت به ناوبری مستقیم دشوارتر است زیرا عملیاتهای ناهمگام عامل زمان را وارد میکنند. از TestDispatcher در Android (kotlinx-coroutines-test) و XCTestExpectation در iOS برای شبیهسازی بارگذاری داده استفاده کنید و بررسی کنید که ناوبری طبق مسیر مورد انتظار انجام میشود. سرویسهای احراز هویت و دیپلینک را برای آزمایش جداگانه هر سناریو mock کنید.
سوالات متداول
Deferred Navigation یک الگوی انتقال معوق است که میتواند در هر سناریوی ناهمگام استفاده شود. Deep Link یکی از محرکهای deferred navigation است اما نه تنها محرک. احراز هویت و feature flagها نیز از ناوبری معوق استفاده میکنند.
بله، بیشتر برنامهها از رویکرد ترکیبی استفاده میکنند. صفحه لیست محصولات (بدون وابستگیهای ناهمگام) میتواند از ناوبری مستقیم استفاده کند و صفحه جزئیات با بارگذاری داده از deferred. تقسیمبندی توسط معماری صفحه خاص تعیین میشود.
استفاده کنید از SharedFlow بدون replay در Android و combineLatest بدون بافر در iOS. اشتراکهای قبلی را با محرک جدید لغو کنید. این تضمین میکند که فقط آخرین رویداد ناوبری پردازش میشود.
بله، در Compose deferred navigation از طریق اشتراک در StateFlow ViewModel و فراخوانی NavController.navigate در LaunchedEffect پیادهسازی میشود. Google استفاده از Navigation Compose را با مدل رویدادی برای سناریوهای deferred توصیه میکند.
به تعویق بیندازید ناوبری را تا زمانی که برنامه دوباره فعال شود. در Android از Lifecycle.State.STARTED برای فیلتر رویدادها استفاده کنید. در iOS وضعیت UIApplication.State را در بلوک Combine یا async/await بررسی کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید