A Deferred Navigation egy halasztott navigációs minta, amelyben a következő képernyőre való átlépés az aszinkron művelet befejezése után történik, nem közvetlenül a felhasználói akció pillanatában. A Android Developers (2024) szerint a deferred navigation lehetővé teszi a versenyhelyzet elkerülését a navigáció és az adatbetöltés között, valamint egyszerűsíti a push értesítésekből és Deeplinkből érkező átmenetek kezelését. A kulcsfontosságú különbség — az útvonal kiszámítása az összes szükséges adat rendelkezésre állása után történik.
Főbb pontok
A Deferred Navigation egy architekturális minta, amelyben a navigációs döntés addig halasztódik, amíg az összes szükséges adat elérhetővé nem válik. Ellentétben a közvetlen átmenettel, ahol a felhasználó megnyom egy gombot és azonnal egy új képernyőre kerül, a deferred navigation elválasztja az esemény-triggert és a tényleges átmenetet, aszinkron műveletet iktatva közéjük.
Architekturálisan a Deferred Navigation állapotváltozáson alapul: a gomb megnyomása elindít egy aszinkron folyamatot, és az eredményre való feliratkozás elindítja a navigációt. Ez különösen fontos az MVVM vagy MVI architektúrájú alkalmazásokban, ahol a ViewModel kezeli az állapotot, a View (Activity, Fragment, SwiftUI View) pedig feliratkozik a változásokra és átmenettel reagál. Ez a megközelítés megszünteti a közvetlen függőséget a UI és a navigációs logika között.
A Google I/O 2023 adatai szerint a deferred navigation minden olyan forgatókönyvhöz ajánlott, ahol a navigáció hálózati kérés eredményétől, hitelesítési ellenőrzéstől, konfigurációbetöltéstől vagy engedélyektől függ. A minta kötelező a Deeplink feldolgozásakor is, amikor az alkalmazásnak először el kell indulnia, be kell töltenie a gyökérképernyőt, és csak azután léphet a célútvonalra.
A Deferred Navigation olyan forgatókönyvekben alkalmazandó, ahol a közvetlen átmenet a képernyő helytelen állapotához vagy betöltési hibákhoz vezet. Nézzük meg azt a négy fő esetet, amikor a deferred navigation kötelező.
Ha a felhasználó védett tartalomra kattint, az alkalmazásnak először ellenőriznie kell a hozzáférési tokent. A közvetlen navigáció a tartalomképernyőre üres képernyőhöz vagy 401-es hibához vezet, ha a token lejárt. A Deferred navigation ellenőrzi a tokent, és csak siker esetén lép a célképernyőre. Sikertelenség esetén átirányít a bejelentkezési képernyőre.
Amikor az alkalmazás egy külső hivatkozáson keresztül nyílik meg, először be kell tölteni a gyökérképernyőt, helyre kell állítani a navigációs állapotot, és csak azután végrehajtani az átmenetet a Deeplinken keresztül. A közvetlen navigáció a célképernyőre gyökérkontextus nélkül anomáliákhoz vezet: üres navigációs veremhez vagy sérült back stackhez.
Egy push értesítésre koppintva az alkalmazás három állapotban lehet: zárt, háttérben vagy aktív. A Deferred Navigation meghatározza az alkalmazás állapotát, betölti a szükséges tartalmat, és csak azután jeleníti meg a célképernyőt. Az iOS lehetővé teszi ennek a forgatókönyvnek a feldolgozását az UNNotificationContentExtension segítségével.
Ha a képernyő funkcióit egy szerveroldali feature flag vezérli, a deferred navigation lehetővé teszi a konfiguráció előzetes lekérését és csak azután a képernyő megjelenítését. Ha a funkció ki van kapcsolva, a felhasználó alternatív tartalmat vagy helyettesítőt lát üres képernyő helyett.
| Forgatókönyv | Közvetlen navigáció | Deferred Navigation |
|---|---|---|
| Hitelesítés | Üres képernyő lejárt tokennél | Átirányítás a bejelentkezéshez |
| Deeplink | Sérült back stack | Helyes navigációs verem |
| Push | Betöltés kontextus nélkül | Adatok készen az átmenet előtt |
| Feature flag | Nem elérhető funkció megjelenítése | Helyettesítő vagy alternatíva |
A közvetlen navigáció (direct navigation) egy hagyományos megközelítés, amelyben az átmenet azonnal megtörténik egy eseményre válaszul. A felhasználó megnyom egy gombot, és az UI router azonnal képernyőt vált. Ez a megközelítés egyszerű és kiszámítható, de korlátozott azokban a forgatókönyvekben, ahol szerveradatokat vagy feltételek ellenőrzését igényel.
A Deferred Navigation egy réteget ad hozzá aszinkron állapot formájában. A felhasználói esemény elindít egy műveletet, és az eredményre való feliratkozás vezérli a navigációt. Ez növeli a kód komplexitását, de rugalmasságot biztosít: ugyanaz a trigger különböző képernyőkre vezethet a betöltött adatoktól függően.
A választás a két megközelítés között a követelményektől függ: ha a képernyő megjelenítéséhez nincs szükség aszinkron adatokra, használjon közvetlen navigációt. Ha a képernyő egy kérés eredményétől, hitelesítéstől vagy külső feltételektől függ, a deferred navigation kötelező. A hibrid megközelítés, ahol az átmenetek egy része közvetlen, egy része pedig halasztott, a legelterjedtebb gyakorlat a termelési alkalmazásokban.
Az Android Jetpack mechanizmusokat biztosít a Deferred Navigation architekturális szintű megvalósításához. A fő ötlet — a ViewModel kezeli az állapotot, az Activity vagy Fragment pedig feliratkozik a változásokra és elindítja a navigációt a NavControlleren keresztül.
A StateFlow a Kotlin coroutines-ben ideális eszköz a deferred navigationhoz. A ViewModel frissíti a StateFlow-t a navigációs eseménnyel, az Activity pedig megfigyeli azt és végrehajtja az átmenetet. Az esemény feldolgozása után a StateFlow törlődik, megakadályozva az ismételt navigációt.
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))
}
}
}
Az Activity-ben a navigációra való feliratkozás elindítja a NavControllert a ViewModel útvonalával. A képernyőelforgatáskor történő ismételt navigáció megelőzésére NavigationEventWrapper használatos, amely az eseményt csak egyszer dolgozza fel. A Jetpack Navigation 2.7+ támogatja a Safe Args-ot a típusbiztos argumentumátadáshoz.
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)
}
}
}
}
}
Az iOS nem rendelkezik beépített Navigation Componenttel, amely az Android Jetpackhez hasonlítana, ezért a fejlesztők a Deferred Navigation-ot a Coordinator Pattern segítségével valósítják meg Combine vagy async/await használatával. A Coordinator kezeli a képernyővermet és a betöltött adatok alapján hoz navigációs döntéseket.
A Coordinator egy objektum, amely a ViewControllerek közötti navigációt kezeli. A Combine-nal együttműködve a ViewModel eseményeket tesz közzé a PassthroughSubjecten keresztül, a Coordinator pedig feliratkozik rájuk és végrehajtja az átmenetet. Ez a megközelítés teljesen elkülöníti a UI-t a navigációs logikától, és megfelel az Apple alkalmazásarchitektúrára vonatkozó ajánlásainak.
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)
}
}
A Swift 5.5 bevezette a structured concurrencet, amely lehetővé teszi a halasztott navigáció megvalósítását async/await segítségével Combine nélkül. A ViewModel egy async függvényt biztosít, amely az adatok betöltése után visszaadja az útvonalat. A Coordinator ezt a függvényt egy Task-ban hívja meg, és a kapott útvonal szerint hajtja végre az átmenetet.
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)
}
}
// A Coordinator-ban:
Task {
if let route = await viewModel.resolveDeeplink(url) {
navigateTo(route)
}
}
A Deferred Navigation egyszerűsíti az aszinkron forgatókönyvek kezelését, de fegyelmezett állapotkezelést igényel. Nézzük meg a fejlesztők által a deferred navigation bevezetésekor elkövetett főbb hibákat.
A leggyakoribb hiba — a deferred navigation végrehajtásának megkísérlése, mielőtt a gyökérképernyő teljesen inicializálódott és a NavController vagy Coordinator készen áll az átmenetre. Androidban ez IllegalStateException-hez, iOS-ben pedig meghatározatlan UI-állapothoz vezet. Megoldás — győződjön meg róla, hogy a komponens életciklusa STARTED vagy RESUMED állapotban van a navigáció elindítása előtt.
Ha a StateFlow vagy Subject nem törli az eseményt a feldolgozás után, a korábbi képernyőre visszatérve a felhasználó automatikusan ugyanarra a képernyőre irányítható át újra. Használjon SharedFlow-t replay=0 beállítással Androidban vagy CurrentValueSubject-et nil értékkel a feldolgozás után iOS-ben, hogy a navigációs esemény csak egyszer aktiválódjon.
Használjon egy központi komponenst a teljes navigációhoz az alkalmazásban. Amikor minden Activity, Fragment vagy ViewController saját navigációs vezérlővel rendelkezik, a deferred navigation az alkalmazás különböző részei között kaotikussá válik. Egyetlen Coordinator megkönnyíti a navigációs forgatókönyvek hibakeresését és tesztelését.
A Deferred Navigation tesztelése nehezebb, mint a közvetlen navigációé, mivel az aszinkron műveletek időtényezőt vezetnek be. Használjon TestDispatcheret Androidban (kotlinx-coroutines-test) és XCTestExpectationt iOS-ben az adatbetöltés szimulálásához, és ellenőrizze, hogy a navigáció a várt útvonalon történik-e. Mockolja a hitelesítési és deeplink szolgáltatásokat az egyes forgatókönyvek izolált teszteléséhez.
Gyakran ismételt kérdések
A Deferred Navigation egy halasztott átmeneti minta, amely bármilyen aszinkron forgatókönyvben alkalmazható. A Deep Link az egyik trigger a deferred navigation számára, de nem az egyetlen. A hitelesítés és a feature flag-ek is használnak halasztott navigációt.
Igen, a legtöbb alkalmazás hibrid megközelítést használ. A terméklista képernyő (aszinkron függőségek nélkül) használhat közvetlen navigációt, az adatbetöltéssel rendelkező részletképernyő pedig deferred-t. A felosztást az adott képernyő architektúrája határozza meg.
Használjon SharedFlow-t replay nélkül Androidban és combineLatest-et pufferelés nélkül iOS-ben. Szüntesse meg a korábbi feliratkozásokat új trigger esetén. Ez garantálja, hogy csak az utolsó navigációs esemény kerül feldolgozásra.
Igen, Compose-ban a deferred navigation a ViewModel StateFlow-jára való feliratkozással és a NavController.navigate meghívásával valósítható meg LaunchedEffect-ben. A Google a Navigation Compose használatát ajánlja esemény-modellel a deferred forgatókönyvekhez.
Halassza el a navigációt addig, amíg az alkalmazás újra aktívvá nem válik. Androidban használja a Lifecycle.State.STARTED-et az események szűréséhez. iOS-ben ellenőrizze az UIApplication.State-et a Combine vagy async/await blokkban.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is