Deferred Navigation este un model de navigare amânată în care trecerea la următorul ecran are loc după finalizarea unei operații asincrone, nu direct în momentul acțiunii utilizatorului. Potrivit Android Developers (2024), deferred navigation permite evitarea stării de cursă între navigare și încărcarea datelor, precum și simplificarea gestionării tranzițiilor din notificări push și Deeplink. Diferența cheie — ruta este calculată după ce toate datele necesare sunt disponibile.
Principalele puncte
Deferred Navigation este un model arhitectural în care decizia de navigare este amânată până când toate datele necesare devin disponibile. Spre deosebire de tranziția directă, unde utilizatorul apasă un buton și ajunge imediat pe un nou ecran, deferred navigation separă evenimentul declanșator de tranziția efectivă, adăugând o operație asincronă între ele.
Arhitectural, Deferred Navigation se bazează pe schimbarea stării: apăsarea butonului inițiază un proces asincron, iar abonarea la rezultatul său declanșează navigarea. Acest lucru este deosebit de important în aplicațiile cu arhitectura MVVM sau MVI, unde ViewModel gestionează starea, iar View (Activity, Fragment, SwiftUI View) se abonează la modificări și reacționează prin tranziție. O astfel de abordare elimină dependența directă între UI și logica de navigare.
Potrivit Google I/O 2023, deferred navigation este recomandată pentru toate scenariile în care navigarea depinde de rezultatul unei cereri de rețea, verificarea autentificării, încărcarea configurației sau permisiunilor. Modelul este de asemenea obligatoriu la procesarea Deeplink, când aplicația trebuie mai întâi să pornească, să încarce ecranul rădăcină și abia apoi să treacă la ruta țintă.
Deferred Navigation se aplică în scenarii în care tranziția directă duce la o stare incorectă a ecranului sau erori de încărcare. Să analizăm patru cazuri principale când deferred navigation este obligatorie.
Dacă utilizatorul face clic pe conținut protejat, aplicația trebuie mai întâi să verifice tokenul de acces. Navigarea directă la ecranul de conținut va duce la un ecran gol sau eroare 401 dacă tokenul a expirat. Deferred navigation verifică tokenul și doar în caz de succes trece la ecranul țintă. În caz de eșec, redirecționează către ecranul de autentificare.
Când aplicația este deschisă printr-un link extern, mai întâi trebuie încărcat ecranul rădăcină, restaurată starea de navigare și abia apoi executată tranziția prin Deeplink. Navigarea directă la ecranul țintă fără contextul rădăcină va duce la anomalii: stivă de navigare goală sau back stack deteriorat.
La atingerea unei notificări push, aplicația poate fi în trei stări: închisă, în fundal sau activă. Deferred Navigation determină starea aplicației, încarcă conținutul necesar și abia apoi afișează ecranul țintă. iOS permite procesarea acestui scenariu prin UNNotificationContentExtension.
Dacă funcționalitatea ecranului este gestionată de un feature flag de pe server, deferred navigation permite mai întâi solicitarea configurației și abia apoi afișarea ecranului. Dacă funcția este dezactivată, utilizatorul vede conținut alternativ sau un placeholder în locul unui ecran gol.
| Scenariu | Navigare directă | Deferred Navigation |
|---|---|---|
| Autentificare | Ecran gol la token expirat | Redirecționare către autentificare |
| Deeplink | Back stack deteriorat | Stivă de navigare corectă |
| Push | Încărcare fără context | Date gata înainte de tranziție |
| Feature flag | Afișarea funcției indisponibile | Placeholder sau alternativă |
Navigarea directă (direct navigation) — o abordare tradițională în care tranziția se execută imediat ca răspuns la un eveniment. Utilizatorul apasă un buton, iar routerul UI comută imediat ecranul. Această abordare este simplă și previzibilă, dar limitată în scenariile care necesită date de pe server sau verificarea condițiilor.
Deferred Navigation adaugă un strat sub forma unei stări asincrone. Evenimentul utilizatorului declanșează o operație, iar abonarea la rezultat gestionează navigarea. Aceasta crește complexitatea codului, dar oferă flexibilitate: același declanșator poate duce la ecrane diferite în funcție de datele încărcate.
Alegerea între două abordări depinde de cerințe: dacă pentru afișarea ecranului nu sunt necesare date asincrone — utilizați navigarea directă. Dacă ecranul depinde de rezultatul unei cereri, autentificare sau condiții externe — deferred navigation este obligatorie. Abordarea hibridă, unde o parte din tranziții sunt directe și o parte amânate, este cea mai răspândită practică în aplicațiile de producție.
Android Jetpack oferă mecanisme pentru implementarea Deferred Navigation la nivel arhitectural. Ideea principală — ViewModel gestionează starea, iar Activity sau Fragment se abonează la modificări și declanșează navigarea prin NavController.
StateFlow în Kotlin coroutines — instrumentul ideal pentru deferred navigation. ViewModel actualizează StateFlow cu evenimentul de navigare, iar Activity îl observă și execută tranziția. După procesarea evenimentului, StateFlow este curățat, prevenind navigarea repetată.
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))
}
}
}
În Activity, abonarea la navigare declanșează NavController cu ruta din ViewModel. Pentru prevenirea navigării repetate la rotirea ecranului, se utilizează un wrapper NavigationEventWrapper care procesează evenimentul o singură dată. Jetpack Navigation 2.7+ suportă Safe Args pentru transmiterea tip-securizată a argumentelor.
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 nu are un Navigation Component încorporat similar cu Android Jetpack, prin urmare dezvoltatorii implementează Deferred Navigation prin Coordinator Pattern împreună cu Combine sau async/await. Coordinator gestionează stiva de ecrane și ia decizii de navigare pe baza datelor încărcate.
Coordinator — un obiect care gestionează navigarea între ViewController-uri. În combinație cu Combine, ViewModel publică evenimente prin PassthroughSubject, iar Coordinator se abonează la ele și execută tranziția. Această abordare separă complet UI de logica de navigare și respectă recomandările Apple pentru arhitectura aplicațiilor.
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 a introdus structured concurrency, care permite implementarea navigării amânate prin async/await fără Combine. ViewModel oferă o funcție async care returnează ruta după încărcarea datelor. Coordinator apelează această funcție în Task și execută tranziția conform rutei primite.
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)
}
}
// În Coordinator:
Task {
if let route = await viewModel.resolveDeeplink(url) {
navigateTo(route)
}
}
Deferred Navigation simplifică gestionarea scenariilor asincrone, dar necesită o abordare disciplinată a gestionării stării. Să analizăm principalele erori pe care le fac dezvoltatorii la implementarea deferred navigation.
Cea mai frecventă eroare — încercarea de a efectua deferred navigation înainte ca ecranul rădăcină să fie complet inițializat și NavController sau Coordinator să fie pregătit pentru tranziție. În Android, aceasta duce la IllegalStateException, în iOS — la o stare UI nedefinită. Soluția — asigurați-vă că ciclul de viață al componentei se află în starea STARTED sau RESUMED înainte de a declanșa navigarea.
Dacă StateFlow sau Subject nu curăță evenimentul după procesare, la revenirea pe ecranul anterior utilizatorul poate fi redirecționat automat pe același ecran din nou. Utilizați SharedFlow cu replay=0 pe Android sau CurrentValueSubject cu nil după procesare pe iOS, pentru ca evenimentul de navigare să se declanșeze o singură dată.
Utilizați un component central pentru întreaga navigare în aplicație. Când fiecare Activity, Fragment sau ViewController are propriul controler de navigare, deferred navigation între diferite părți ale aplicației devine haotică. Un coordinator unic facilitează depanarea și testarea scenariilor de navigare.
Deferred Navigation este mai dificil de testat decât navigarea directă, deoarece operațiile asincrone introduc un factor de timp. Utilizați TestDispatcher în Android (kotlinx-coroutines-test) și XCTestExpectation în iOS pentru simularea încărcării datelor și verificarea că navigarea se execută pe ruta așteptată. Mocați serviciile de autentificare și deeplink pentru testarea izolată a fiecărui scenariu.
Întrebări frecvente
Deferred Navigation este un model de tranziție amânată care poate fi aplicat în orice scenariu asincron. Deep Link este unul dintre declanșatoarele pentru deferred navigation, dar nu singurul. Autentificarea și feature flag-urile folosesc de asemenea navigarea amânată.
Da, majoritatea aplicațiilor folosesc o abordare hibridă. Ecranul listei de produse (fără dependențe asincrone) poate folosi navigarea directă, iar ecranul de detalii cu încărcare date — deferred. Împărțirea este determinată de arhitectura ecranului specific.
Utilizați SharedFlow fără replay pe Android și combineLatest fără buffer pe iOS. Anulați abonările anterioare la un nou declanșator. Aceasta garantează că se procesează doar ultimul eveniment de navigare.
Da, în Compose deferred navigation se implementează prin abonarea la StateFlow-ul ViewModel și apelarea NavController.navigate în LaunchedEffect. Google recomandă utilizarea Navigation Compose cu modelul evenimentelor pentru scenarii deferred.
Amânați navigarea până când aplicația devine din nou activă. În Android, utilizați Lifecycle.State.STARTED pentru filtrarea evenimentelor. În iOS, verificați UIApplication.State în blocul Combine sau async/await.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și