Deferred Navigation је образац одложене навигације при којем се прелазак на следећи екран дешава након завршетка асинхроне операције, а не непосредно у тренутку акције корисника. Према Android Developers (2024), deferred navigation омогућава избегавање стања трке између навигације и учитавања података, као и поједностављује обраду прелаза из push обавештења и 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 обавезна.
Ако корисник кликне на заштићени садржај, апликација прво мора да провери токен приступа. Директна навигација на екран садржаја довешће до празног екрана или грешке 401 ако је токен истекао. Deferred navigation проверава токен и само при успеху прелази на циљани екран. При неуспеху преусмерава на екран за пријаву.
Када се апликација отвори преко спољног линка, прво треба учитати корени екран, обновити стање навигације и тек онда извршити прелаз путем Deeplink-а. Директна навигација на циљани екран без кореног контекста довешће до аномалија: празног стека навигације или сломљеног back stack-а.
При тапу на push обавештење, апликација може бити у три стања: затворена, у позадини или активна. Deferred Navigation одређује стање апликације, учитава потребан садржај и тек онда приказује циљани екран. iOS омогућава обраду овог сценарија путем UNNotificationContentExtension-а.
Ако функционалност екрана управља feature flag са сервера, deferred navigation омогућава да се прво захтева конфигурација и тек онда прикаже екран. Ако је функција искључена, корисник види алтернативни садржај или placeholder уместо празног екрана.
| Сценариј | Директна навигација | Deferred Navigation |
|---|---|---|
| Ауторизација | Празан екран при истеклом токену | Преусмеравање на пријаву |
| Deeplink | Сломљен back stack | Исправан стек навигације |
| Push | Учитавање без контекста | Подаци спремни пре прелаза |
| Feature flag | Приказ недоступне функције | Placeholder или алтернатива |
Директна навигација (direct navigation) — традиционални приступ при којем се прелаз извршава одмах као одговор на догађај. Корисник притиска дугме, и UI рутер одмах пребацује екран. Овај приступ је једноставан и предвидив, али ограничен у сценаријима где су потребни подаци са сервера или провера услова.
Deferred Navigation додаје слој у облику асинхроног стања. Догађај корисника покреће операцију, а претплата на резултат управља навигацијом. То повећава сложеност кода, али даје флексибилност: исти окидач може водити на различите екране у зависности од учитаних података.
Избор између два приступа зависи од захтева: ако за приказ екрана нису потребни асинхрони подаци — користите директну навигацију. Ако екран зависи од резултата захтева, ауторизације или спољних услова — deferred navigation је обавезна. Хибридни приступ, где је део прелаза директан а део одложен, најчешћа је пракса у индустријским апликацијама.
Android Jetpack пружа механизме за имплементацију Deferred Navigation на архитектонском нивоу. Основна идеја — ViewModel управља стањем, а Activity или Fragment се претплаћује на промене и покреће навигацију путем NavController-а.
StateFlow у Kotlin корутинама — идеалан алат за 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-у за симулацију учитавања података и проверу да се навигација извршава према очекиваној рути. Мокајте сервисе ауторизације и дееплинкова за изоловано тестирање сваког сценарија.
Често постављана питања
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 креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође