Deferred Navigation — چیست، اصول و پیاده‌سازی

نویسنده: IT Sectr منتشر شده: 2026-06-10 زمان مطالعه: 9 دقیقه

Deferred Navigation یک الگوی ناوبری معوق است که در آن انتقال به صفحه بعدی پس از اتمام عملیات ناهمگام انجام می‌شود، نه مستقیماً در لحظه اقدام کاربر. به گفته Android Developers (2024)، deferred navigation از شرط مسابقه بین ناوبری و بارگذاری داده جلوگیری می‌کند و همچنین پردازش انتقال‌های اعلان‌های فشاری و Deeplink را ساده می‌کند. تفاوت کلیدی — مسیر پس از در دسترس بودن تمام داده‌های لازم محاسبه می‌شود.

نکات اصلی

  • Deferred Navigation — ناوبری معوق، جایی که انتقال پس از تکمیل عملیات ناهمگام انجام می‌شود
  • ناوبری مستقیم انتقال را فوراً پردازش می‌کند، deferred منتظر داده می‌ماند
  • سناریوهای معمول: احراز هویت، Deeplink، اعلان‌های فشاری، بارگذاری پیکربندی
  • در Android از طریق Navigation Component با回调 و StateFlow پیاده‌سازی می‌شود
  • در iOS از Combine یا async/await با هماهنگ‌کننده ناوبری استفاده می‌شود

Deferred Navigation چیست

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

وقتی برنامه از طریق لینک خارجی باز می‌شود، ابتدا باید صفحه ریشه بارگذاری شود، وضعیت ناوبری بازیابی شود و سپس انتقال از طریق Deeplink انجام شود. ناوبری مستقیم به صفحه هدف بدون زمینه ریشه منجر به ناهنجاری‌هایی می‌شود: پشته ناوبری خالی یا back stack خراب.

اعلان‌های فشاری با محتوا

با ضربه زدن روی اعلان فشاری، برنامه می‌تواند در سه حالت باشد: بسته، پس‌زمینه یا فعال. Deferred Navigation وضعیت برنامه را تعیین می‌کند، محتوای لازم را بارگذاری می‌کند و سپس صفحه هدف را نمایش می‌دهد. iOS این سناریو را از طریق UNNotificationContentExtension پردازش می‌کند.

Feature flag پویا

اگر عملکرد صفحه توسط feature flag از سرور کنترل می‌شود، deferred navigation ابتدا پیکربندی را درخواست کرده و سپس صفحه را نمایش می‌دهد. اگر ویژگی غیرفعال باشد، کاربر به جای صفحه خالی محتوای جایگزین یا placeholder می‌بیند.

سناریوناوبری مستقیمDeferred Navigation
احراز هویتصفحه خالی با توکن منقضیهدایت به ورود
DeeplinkBack stack خرابپشته ناوبری صحیح
اعلان فشاریبارگذاری بدون زمینهداده قبل از انتقال آماده است
Feature flagنمایش عملکرد غیرقابل دسترسPlaceholder یا جایگزین

Deferred Navigation در مقابل ناوبری مستقیم

ناوبری مستقیم (direct navigation) یک رویکرد سنتی است که در آن انتقال بلافاصله در پاسخ به رویداد انجام می‌شود. کاربر دکمه را فشار می‌دهد و مسیریاب UI فوراً صفحه را تغییر می‌دهد. این رویکرد ساده و قابل پیش‌بینی است، اما در سناریوهایی که نیاز به داده از سرور یا بررسی شرایط است محدود می‌شود.

Deferred Navigation یک لایه به شکل حالت ناهمگام اضافه می‌کند. رویداد کاربر عملیات را شروع می‌کند و اشتراک در نتیجه، ناوبری را مدیریت می‌کند. این پیچیدگی کد را افزایش می‌دهد اما انعطاف‌پذیری می‌دهد: یک محرک واحد می‌تواند بسته به داده‌های بارگذاری‌شده به صفحات مختلف منجر شود.

انتخاب بین دو رویکرد به نیازها بستگی دارد: اگر برای نمایش صفحه به داده ناهمگام نیاز نیست از ناوبری مستقیم استفاده کنید. اگر صفحه به نتیجه درخواست، احراز هویت یا شرایط خارجی وابسته است، deferred navigation الزامی است. رویکرد ترکیبی، جایی که بخشی از انتقال‌ها مستقیم و بخشی معوق هستند، رایج‌ترین روش در برنامه‌های صنعتی است.

پیاده‌سازی در Android: Navigation Component و ViewModel

Android Jetpack مکانیسم‌هایی برای پیاده‌سازی Deferred Navigation در سطح معماری فراهم می‌کند. ایده اصلی این است که ViewModel حالت را مدیریت می‌کند و Activity یا Fragment در تغییرات مشترک شده و از طریق NavController ناوبری را راه‌اندازی می‌کند.

Deferred Navigation با StateFlow

StateFlow در Kotlin coroutines ابزاری ایده‌آل برای deferred navigation است. ViewModel StateFlow را با رویداد ناوبری به‌روز می‌کند و Activity آن را مشاهده کرده و انتقال را انجام می‌دهد. وقتی رویداد پردازش شد StateFlow پاک می‌شود و از ناوبری مجدد جلوگیری می‌کند.

kotlin
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))
        }
    }
}

Deferred Navigation با NavController

در Activity اشتراک در ناوبری، NavController را با مسیر ViewModel راه‌اندازی می‌کند. برای جلوگیری از ناوبری مجدد هنگام چرخش صفحه، از NavigationEventWrapper استفاده می‌شود که رویداد را فقط یک بار پردازش می‌کند. Jetpack Navigation 2.7+ از Safe Args برای انتقال امن آرگومان‌ها پشتیبانی می‌کند.

kotlin
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: Coordinator Pattern و Combine

iOS Navigation Component داخلی مشابه Android Jetpack ندارد، بنابراین توسعه‌دهندگان Deferred Navigation را از طریق Coordinator Pattern همراه با Combine یا async/await پیاده‌سازی می‌کنند. Coordinator پشته صفحات را مدیریت می‌کند و بر اساس داده‌های بارگذاری‌شده درباره ناوبری تصمیم می‌گیرد.

Coordinator با Combine

Coordinator شیئی است که ناوبری بین ViewControllerها را مدیریت می‌کند. در ترکیب با Combine، ViewModel رویدادها را از طریق PassthroughSubject منتشر می‌کند و Coordinator در آنها مشترک شده و انتقال را انجام می‌دهد. این رویکرد UI را کاملاً از منطق ناوبری جدا می‌کند و با توصیه‌های Apple برای معماری برنامه مطابقت دارد.

swift
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)
    }
}

Deferred Navigation با async/await

Swift 5.5 structured concurrency را معرفی کرد که امکان پیاده‌سازی ناوبری معوق را از طریق async/await بدون Combine فراهم می‌کند. ViewModel یک تابع async ارائه می‌دهد که پس از بارگذاری داده مسیر را برمی‌گرداند. Coordinator این تابع را در Task فراخوانی کرده و انتقال را طبق مسیر دریافتی انجام می‌دهد.

swift
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)
    }
}

خطاهای رایج و best practices

Deferred Navigation پردازش سناریوهای ناهمگام را ساده می‌کند اما نیاز به رویکرد منظمی برای مدیریت حالت دارد. بیایید خطاهای اصلی توسعه‌دهندگان هنگام پیاده‌سازی deferred navigation را بررسی کنیم.

خطا: ناوبری قبل از تکمیل مقداردهی اولیه

رایج‌ترین خطا تلاش برای انجام deferred navigation قبل از اینکه صفحه ریشه کاملاً مقداردهی شود و NavController یا Coordinator برای انتقال آماده باشد. در Android این منجر به IllegalStateException و در iOS به وضعیت نامشخص UI می‌شود. راه حل: مطمئن شوید چرخه حیات کامپوننت قبل از شروع ناوبری در حالت STARTED یا RESUMED است.

خطا: ناوبری مجدد هنگام بازگشت به صفحه

اگر StateFlow یا Subject رویداد را پس از پردازش پاک نکند، هنگام بازگشت به صفحه قبلی کاربر ممکن است به طور خودکار دوباره به همان صفحه هدایت شود. از SharedFlow با replay=0 در Android یا CurrentValueSubject با nil پس از پردازش در iOS استفاده کنید تا رویداد ناوبری فقط یک بار رخ دهد.

Best Practice: یک NavController یا Coordinator واحد

از یک کامپوننت مرکزی برای کل ناوبری در برنامه استفاده کنید. وقتی هر Activity، Fragment یا ViewController کنترل‌کننده ناوبری خود را دارد، deferred navigation بین بخش‌های مختلف برنامه آشفته می‌شود. یک Coordinator واحد اشکال‌زدایی و آزمایش سناریوهای ناوبری را آسان‌تر می‌کند.

Best Practice: آزمایش سناریوهای ناوبری معوق

Deferred Navigation نسبت به ناوبری مستقیم دشوارتر است زیرا عملیات‌های ناهمگام عامل زمان را وارد می‌کنند. از TestDispatcher در Android (kotlinx-coroutines-test) و XCTestExpectation در iOS برای شبیه‌سازی بارگذاری داده استفاده کنید و بررسی کنید که ناوبری طبق مسیر مورد انتظار انجام می‌شود. سرویس‌های احراز هویت و دیپ‌لینک را برای آزمایش جداگانه هر سناریو mock کنید.

سوالات متداول

تفاوت Deferred Navigation با Deep Link چیست؟

Deferred Navigation یک الگوی انتقال معوق است که می‌تواند در هر سناریوی ناهمگام استفاده شود. Deep Link یکی از محرک‌های deferred navigation است اما نه تنها محرک. احراز هویت و feature flagها نیز از ناوبری معوق استفاده می‌کنند.

آیا می‌توان deferred و direct navigation را ترکیب کرد؟

بله، بیشتر برنامه‌ها از رویکرد ترکیبی استفاده می‌کنند. صفحه لیست محصولات (بدون وابستگی‌های ناهمگام) می‌تواند از ناوبری مستقیم استفاده کند و صفحه جزئیات با بارگذاری داده از deferred. تقسیم‌بندی توسط معماری صفحه خاص تعیین می‌شود.

چگونه از شرط مسابقه در deferred navigation جلوگیری کنیم؟

استفاده کنید از SharedFlow بدون replay در Android و combineLatest بدون بافر در iOS. اشتراک‌های قبلی را با محرک جدید لغو کنید. این تضمین می‌کند که فقط آخرین رویداد ناوبری پردازش می‌شود.

آیا Jetpack Compose از deferred navigation پشتیبانی می‌کند؟

بله، در Compose deferred navigation از طریق اشتراک در StateFlow ViewModel و فراخوانی NavController.navigate در LaunchedEffect پیاده‌سازی می‌شود. Google استفاده از Navigation Compose را با مدل رویدادی برای سناریوهای deferred توصیه می‌کند.

چگونه deferred navigation را در برنامه کمینه‌شده مدیریت کنیم؟

به تعویق بیندازید ناوبری را تا زمانی که برنامه دوباره فعال شود. در Android از Lifecycle.State.STARTED برای فیلتر رویدادها استفاده کنید. در iOS وضعیت UIApplication.State را در بلوک Combine یا async/await بررسی کنید.

خلاصه

  • Deferred Navigation — الگویی که در آن انتقال پس از تکمیل عملیات ناهمگام انجام می‌شود
  • سناریوهای اصلی: احراز هویت، Deeplink، اعلان‌های فشاری، feature flagها
  • StateFlow/SharedFlow در Android و Combine/async-await در iOS — ابزارهای پیاده‌سازی
  • Deferred Navigation برخلاف مستقیم باعث شرط مسابقه در بارگذاری ناهمگام نمی‌شود
  • Navigation Component در Android و Coordinator Pattern در iOS — معماری‌های پایه
  • یک NavController/Coordinator واحد برای ناوبری سازگار الزامی است
  • آزمایش deferred navigation به TestDispatcher و mock برای سرویس‌های ناهمگام نیاز دارد

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

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

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

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