Deferred Navigation (Відкладена навігація) — що це, принципи та реалізація

Автор: IT Sectr Опубліковано: 2026-06-10 Час читання: 9 хв

Deferred Navigation (Відкладена навігація) — це патерн відкладеної навігації, за якого перехід на наступний екран відбувається після завершення асинхронної операції, а не безпосередньо в момент дії користувача. За даними Android Developers (2024), відкладена навігація дозволяє уникнути стану гонки між навігацією та завантаженням даних, а також спрощує обробку переходів із push-повідомлень і Deeplink. Ключова відмінність — маршрут обчислюється після того, як усі необхідні дані доступні.

Головне

  • Deferred Navigation — відкладена навігація, де перехід виконується після завершення асинхронних операцій
  • Пряма навігація обробляє перехід миттєво, deferred — чекає на дані
  • Типові сценарії: авторизація, Deeplink, push-повідомлення, завантаження конфігурації
  • В Android реалізується через Navigation Component з колбеками та StateFlow
  • В iOS використовується Combine або async/await з координатором навігації

Що таке Deferred Navigation

Deferred Navigation (Відкладена навігація) — це архітектурний патерн, за якого навігаційне рішення відкладається до моменту, коли стануть доступні всі необхідні дані. На відміну від прямого переходу, де користувач натискає кнопку та одразу потрапляє на новий екран, відкладена навігація розділяє подію-тригер і фактичний перехід, додаючи між ними асинхронну операцію.

Архітектурно Deferred Navigation будується на зміні стану: натискання кнопки ініціює асинхронний процес, а підписка на його результат запускає навігацію. Це особливо важливо в застосунках з архітектурою MVVM або MVI, де ViewModel керує станом, а View (Activity, Fragment, SwiftUI View) підписується на зміни та реагує переходом. Такий підхід усуває пряму залежність між UI та навігаційною логікою.

За даними Google I/O 2023, відкладена навігація рекомендована для всіх сценаріїв, де навігація залежить від результату мережевого запиту, перевірки авторизації, завантаження конфігурації або дозволів. Патерн також обов'язковий під час обробки Deeplink, коли застосунок має спочатку запуститися, завантажити кореневий екран і лише потім перейти на цільовий маршрут.

Коли потрібна відкладена навігація

Deferred Navigation застосовується в сценаріях, де прямий перехід призводить до некоректного стану екрана або помилок завантаження. Розглянемо чотири основні випадки, коли відкладена навігація обов'язкова.

Авторизація та аутентифікація

Якщо користувач натискає на захищений контент, застосунок має спочатку перевірити токен доступу. Пряма навігація на екран контенту призведе до порожнього екрана або помилки 401, якщо токен закінчився. Відкладена навігація перевіряє токен, і лише при успіху — переходить на цільовий екран. При невдачі — перенаправляє на екран входу.

Обробка Deeplink

Коли застосунок відкривається за зовнішнім посиланням, спочатку потрібно завантажити кореневий екран, відновити стан навігації та лише потім виконати перехід за Deeplink. Пряма навігація на цільовий екран без кореневого контексту призведе до аномалій: порожнього стеку навігації або зламаного back stack.

Push-повідомлення з контентом

При тапі по push-повідомленню застосунок може перебувати в трьох станах: закритий, у фоні або активний. Deferred Navigation визначає стан застосунку, завантажує необхідний контент і лише потім показує цільовий екран. iOS дозволяє обробити цей сценарій через UNNotificationContentExtension.

Динамічний фіче-флаг

Якщо функціональність екрана керується фіче-флагом із сервера, відкладена навігація дозволяє спочатку запитати конфігурацію та лише потім показати екран. Якщо фіча вимкнена — користувач бачить альтернативний контент або заглушку замість порожнього екрана.

СценарійПряма навігаціяDeferred Navigation
АвторизаціяПорожній екран при простроченому токеніПеренаправлення на логін
DeeplinkЗламаний back stackКоректний стек навігації
PushЗавантаження без контекстуДані готові до переходу
Фіче-флагВідображення недоступної функціїЗаглушка або альтернатива

Deferred Navigation vs пряма навігація

Пряма навігація — традиційний підхід, за якого перехід виконується негайно у відповідь на подію. Користувач натискає кнопку, і UI роутер одразу перемикає екран. Цей підхід простий і передбачуваний, але обмежений у сценаріях, де потрібні дані з сервера або перевірка умов.

Deferred Navigation додає прошарок у вигляді асинхронного стану. Подія користувача запускає операцію, а підписка на результат керує навігацією. Це збільшує складність коду, але дає гнучкість: один і той самий тригер може вести на різні екрани залежно від завантажених даних.

Вибір між двома підходами залежить від вимог: якщо для відображення екрана не потрібні асинхронні дані — використовуйте пряму навігацію. Якщо екран залежить від результату запиту, авторизації або зовнішніх умов — відкладена навігація обов'язкова. Гібридний підхід, де частина переходів пряма, а частина — відкладена, є найпоширенішою практикою в промислових застосунках.

Реалізація в Android: Navigation Component і ViewModel

Android Jetpack надає механізми для реалізації Deferred Navigation на рівні архітектури. Основна ідея — ViewModel керує станом, а Activity або Fragment підписується на зміни та запускає навігацію через NavController.

Deferred Navigation з StateFlow

StateFlow у Kotlin корутинах — ідеальний інструмент для відкладеної навігації. 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)
    }
}

// In Coordinator:
Task {
    if let route = await viewModel.resolveDeeplink(url) {
        navigateTo(route)
    }
}

Типові помилки та best practices

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 має власний навігаційний контролер, відкладена навігація між різними частинами застосунку стає хаотичною. Єдиний Coordinator полегшує налагодження та тестування навігаційних сценаріїв.

Best Practice: тестування сценаріїв відкладеної навігації

Deferred Navigation складніше тестувати, ніж пряму, оскільки асинхронні операції вносять фактор часу. Використовуйте TestDispatcher в Android (kotlinx-coroutines-test) та XCTestExpectation в iOS для симуляції завантаження даних і перевірки, що навігація виконується за очікуваним маршрутом. Мокайте сервіси авторизації та деплінків для ізольованого тестування кожного сценарію.

Часті запитання

Чим Deferred Navigation відрізняється від Deep Link?

Deferred Navigation — це патерн відкладеного переходу, який може застосовуватися в будь-яких асинхронних сценаріях. Deep Link — один із тригерів для відкладеної навігації, але не єдиний. Авторизація та фіче-флаги теж використовують відкладену навігацію.

Чи можна комбінувати deferred і direct navigation?

Так, більшість застосунків використовують гібридний підхід. Екран списку товарів (без асинхронних залежностей) може використовувати пряму навігацію, а екран деталей із завантаженням даних — deferred. Розділення визначається архітектурою конкретного екрана.

Як уникнути race condition при deferred navigation?

Використовуйте SharedFlow без replay на Android та combineLatest без буферизації на iOS. Скасовуйте попередні підписки при новому тригері. Це гарантує, що обробляється лише остання навігаційна подія.

Чи підтримує Jetpack Compose deferred navigation?

Так, в Compose відкладена навігація реалізується через підписку на StateFlow ViewModel та виклик NavController.navigate в LaunchedEffect. Google рекомендує використовувати Navigation Compose з подійною моделлю для deferred сценаріїв.

Як обробити deferred navigation при згорнутому застосунку?

Відкладіть навігацію до моменту, коли застосунок знову стане активним. В Android використовуйте Lifecycle.State.STARTED для фільтрації подій. В iOS перевірте UIApplication.State в Combine або async/await блоці.

Підсумки

  • Deferred Navigation — патерн, за якого перехід виконується після завершення асинхронної операції
  • Основні сценарії: авторизація, Deeplink, push-повідомлення, фіче-флаги
  • StateFlow/SharedFlow в Android та Combine/async-await в iOS — інструменти реалізації
  • Deferred Navigation на відміну від прямої не викликає стан гонки при асинхронному завантаженні
  • Navigation Component в Android та Coordinator Pattern в iOS — базові архітектури
  • Єдиний NavController/Coordinator обов'язковий для консистентної навігації
  • Тестування deferred navigation вимагає TestDispatcher та моків для асинхронних сервісів

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також