Deferred Navigation je vzor odložené navigace, při kterém přechod na další obrazovku nastává po dokončení asynchronní operace, nikoli přímo v okamžiku akce uživatele. Podle Android Developers (2024) umožňuje deferred navigation vyhnout se závodním stavům mezi navigací a načítáním dat a také zjednodušuje zpracování přechodů z push oznámení a Deeplink. Klíčový rozdíl — trasa se vypočítá až po zpřístupnění všech potřebných dat.
Hlavní body
Deferred Navigation je architektonický vzor, při kterém je rozhodnutí o navigaci odloženo, dokud nejsou k dispozici všechna potřebná data. Na rozdíl od přímého přechodu, kde uživatel stiskne tlačítko a okamžitě se dostane na novou obrazovku, deferred navigation odděluje spouštěcí událost a skutečný přechod a vkládá mezi ně asynchronní operaci.
Architektonicky je Deferred Navigation založena na změně stavu: stisknutí tlačítka iniciuje asynchronní proces a přihlášení k odběru jeho výsledku spouští navigaci. To je zvláště důležité v aplikacích s architekturou MVVM nebo MVI, kde ViewModel spravuje stav a View (Activity, Fragment, SwiftUI View) se přihlašuje ke změnám a reaguje přechodem. Tento přístup odstraňuje přímou závislost mezi UI a logikou navigace.
Podle Google I/O 2023 je deferred navigation doporučena pro všechny scénáře, kde navigace závisí na výsledku síťového požadavku, ověření autorizace, načtení konfigurace nebo oprávnění. Vzor je také povinný při zpracování Deeplink, když aplikace musí nejprve spustit, načíst kořenovou obrazovku a teprve poté přejít na cílovou trasu.
Deferred Navigation se používá ve scénářích, kde přímý přechod vede k nesprávnému stavu obrazovky nebo chybám načítání. Podívejme se na čtyři hlavní případy, kdy je deferred navigation povinná.
Pokud uživatel klikne na chráněný obsah, aplikace musí nejprve zkontrolovat přístupový token. Přímá navigace na obrazovku obsahu povede k prázdné obrazovce nebo chybě 401, pokud token vypršel. Deferred navigation zkontroluje token a pouze při úspěchu přejde na cílovou obrazovku. Při neúspěchu přesměruje na přihlašovací obrazovku.
Když je aplikace otevřena prostřednictvím externího odkazu, je nejprve třeba načíst kořenovou obrazovku, obnovit stav navigace a teprve poté provést přechod přes Deeplink. Přímá navigace na cílovou obrazovku bez kořenového kontextu povede k anomáliím: prázdnému zásobníku navigace nebo poškozenému back stack.
Při klepnutí na push oznámení může být aplikace ve třech stavech: zavřená, na pozadí nebo aktivní. Deferred Navigation určuje stav aplikace, načte potřebný obsah a teprve poté zobrazí cílovou obrazovku. iOS umožňuje zpracovat tento scénář přes UNNotificationContentExtension.
Pokud je funkčnost obrazovky řízena feature flag ze serveru, deferred navigation umožňuje nejprve vyžádat konfiguraci a teprve poté zobrazit obrazovku. Pokud je funkce vypnuta, uživatel vidí alternativní obsah nebo placeholder místo prázdné obrazovky.
| Scénář | Přímá navigace | Deferred Navigation |
|---|---|---|
| Autorizace | Prázdná obrazovka při vypršelém tokenu | Přesměrování na přihlášení |
| Deeplink | Poškozený back stack | Správný zásobník navigace |
| Push | Načítání bez kontextu | Data připravena před přechodem |
| Feature flag | Zobrazení nedostupné funkce | Placeholder nebo alternativa |
Přímá navigace (direct navigation) — tradiční přístup, při kterém se přechod provádí okamžitě jako reakce na událost. Uživatel stiskne tlačítko a směrovač UI okamžitě přepne obrazovku. Tento přístup je jednoduchý a předvídatelný, ale omezený ve scénářích, kde jsou potřeba data ze serveru nebo kontrola podmínek.
Deferred Navigation přidává vrstvu v podobě asynchronního stavu. Událost uživatele spouští operaci a přihlášení k odběru výsledku řídí navigaci. To zvyšuje složitost kódu, ale poskytuje flexibilitu: stejná spouštěč může vést k různým obrazovkám v závislosti na načtených datech.
Volba mezi dvěma přístupy závisí na požadavcích: pokud pro zobrazení obrazovky nejsou potřeba asynchronní data — použijte přímou navigaci. Pokud obrazovka závisí na výsledku požadavku, autorizaci nebo externích podmínkách — je deferred navigation povinná. Hybridní přístup, kde část přechodů je přímá a část odložená, je nejběžnější praxí v produkčních aplikacích.
Android Jetpack poskytuje mechanismy pro implementaci Deferred Navigation na architektonické úrovni. Hlavní myšlenka — ViewModel spravuje stav a Activity nebo Fragment se přihlašuje ke změnám a spouští navigaci přes NavController.
StateFlow v Kotlin coroutines — ideální nástroj pro deferred navigation. ViewModel aktualizuje StateFlow navigační událostí a Activity ji pozoruje a provádí přechod. Jakmile je událost zpracována, StateFlow je vymazán, čímž se zabrání opakované navigaci.
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))
}
}
}
V Activity přihlášení k odběru navigace spouští NavController s trasou z ViewModel. Aby se zabránilo opakované navigaci při otáčení obrazovky, používá se wrapper NavigationEventWrapper, který zpracovává událost pouze jednou. Jetpack Navigation 2.7+ podporuje Safe Args pro typově bezpečné předávání argumentů.
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 nemá vestavěný Navigation Component podobný Android Jetpack, proto vývojáři implementují Deferred Navigation přes Coordinator Pattern v kombinaci s Combine nebo async/await. Coordinator spravuje zásobník obrazovek a rozhoduje o navigaci na základě načtených dat.
Coordinator — objekt, který spravuje navigaci mezi ViewController. V kombinaci s Combine ViewModel publikuje události přes PassthroughSubject a Coordinator se k nim přihlašuje a provádí přechod. Tento přístup zcela odděluje UI od logiky navigace a je v souladu s doporučeními Apple pro architekturu aplikací.
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 zavedl structured concurrency, který umožňuje implementovat odloženou navigaci přes async/await bez Combine. ViewModel poskytuje async funkci, která po načtení dat vrací trasu. Coordinator volá tuto funkci v Task a provádí přechod podle získané trasy.
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)
}
}
// V Coordinator:
Task {
if let route = await viewModel.resolveDeeplink(url) {
navigateTo(route)
}
}
Deferred Navigation zjednodušuje zpracování asynchronních scénářů, ale vyžaduje disciplinovaný přístup ke správě stavu. Podívejme se na hlavní chyby, kterých se vývojáři dopouštějí při implementaci deferred navigation.
Nejčastější chyba — pokus o provedení deferred navigation před tím, než je kořenová obrazovka plně inicializována a NavController nebo Coordinator je připraven k přechodu. V Androidu to vede k IllegalStateException, v iOS — k nedefinovanému stavu UI. Řešení — ujistěte se, že životní cyklus komponenty je ve stavu STARTED nebo RESUMED před spuštěním navigace.
Pokud StateFlow nebo Subject nevyčistí událost po zpracování, při návratu na předchozí obrazovku může být uživatel automaticky přesměrován znovu na stejnou obrazovku. Použijte SharedFlow s replay=0 na Androidu nebo CurrentValueSubject s nil po zpracování na iOS, aby se navigační událost spustila pouze jednou.
Použijte jeden centrální komponent pro celou navigaci v aplikaci. Když má každé Activity, Fragment nebo ViewController svůj vlastní navigační ovladač, deferred navigation mezi různými částmi aplikace se stává chaotickou. Jeden Coordinator usnadňuje ladění a testování navigačních scénářů.
Deferred Navigation se testuje hůře než přímá navigace, protože asynchronní operace vnášejí časový faktor. Použijte TestDispatcher v Androidu (kotlinx-coroutines-test) a XCTestExpectation v iOS pro simulaci načítání dat a ověření, že navigace probíhá podle očekávané trasy. Mockujte autorizační a deeplink služby pro izolované testování každého scénáře.
Často kladené otázky
Deferred Navigation je vzor odloženého přechodu, který lze použít v jakémkoli asynchronním scénáři. Deep Link je jedním ze spouštěčů deferred navigation, ale ne jediným. Autorizace a feature flagy také používají odloženou navigaci.
Ano, většina aplikací používá hybridní přístup. Obrazovka se seznamem produktů (bez asynchronních závislostí) může používat přímou navigaci a obrazovka detailů s načítáním dat — deferred. Rozdělení je určeno architekturou konkrétní obrazovky.
Použijte SharedFlow bez replay na Androidu a combineLatest bez bufferování na iOS. Zrušte předchozí odběry při novém spouštěči. Tím je zaručeno, že se zpracovává pouze poslední navigační událost.
Ano, v Compose se deferred navigation implementuje přihlášením k odběru StateFlow ViewModel a voláním NavController.navigate v LaunchedEffect. Google doporučuje používat Navigation Compose s událostním modelem pro deferred scénáře.
Odložte navigaci, dokud se aplikace znovu nestane aktivní. V Androidu použijte Lifecycle.State.STARTED pro filtrování událostí. V iOS zkontrolujte UIApplication.State v bloku Combine nebo async/await.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také