Deferred Navigation — cos'è, principi e implementazione

Autore: IT Sectr Pubblicato: 2026-06-10 Tempo di lettura: 9 min

Deferred Navigation è un pattern di navigazione differita in cui la transizione allo schermo successivo avviene dopo il completamento di un'operazione asincrona, anziché direttamente nel momento dell'azione dell'utente. Secondo Android Developers (2024), la navigazione differita aiuta a evitare race condition tra navigazione e caricamento dati, e semplifica la gestione delle transizioni da notifiche push e Deeplink. La differenza chiave — il percorso viene calcolato dopo che tutti i dati necessari sono disponibili.

Punti chiave

  • Deferred Navigation — navigazione differita dove la transizione avviene dopo operazioni asincrone
  • La navigazione diretta gestisce la transizione istantaneamente, la differita attende i dati
  • Scenari tipici: autenticazione, Deeplink, notifiche push, caricamento configurazione
  • Su Android viene implementata tramite Navigation Component con callback e StateFlow
  • Su iOS si utilizza Combine o async/await con un coordinatore di navigazione

Cos'è Deferred Navigation

Deferred Navigation è un pattern architetturale in cui la decisione di navigazione viene rimandata fino a quando tutti i dati necessari sono disponibili. A differenza di una transizione diretta in cui l'utente preme un pulsante e arriva immediatamente a un nuovo schermo, la navigazione differita separa l'evento trigger e la transizione effettiva inserendo un'operazione asincrona tra di essi.

Architetturalmente, Deferred Navigation si basa sul cambiamento di stato: la pressione di un pulsante avvia un processo asincrono, e una sottoscrizione al suo risultato attiva la navigazione. Questo è particolarmente importante nelle applicazioni con architettura MVVM o MVI, dove la ViewModel gestisce lo stato e la View (Activity, Fragment, SwiftUI View) si sottoscrive ai cambiamenti e reagisce con una transizione. Questo approccio elimina la dipendenza diretta tra UI e logica di navigazione.

Secondo Google I/O 2023, la navigazione differita è raccomandata per tutti gli scenari in cui la navigazione dipende dal risultato di una richiesta di rete, verifica di autenticazione, caricamento di configurazione o permessi. Il pattern è anche obbligatorio nella gestione dei Deeplink, dove l'app deve prima avviarsi, caricare lo schermo principale e solo poi navigare verso il percorso di destinazione.

Quando serve la navigazione differita

Deferred Navigation viene utilizzata in scenari in cui la navigazione diretta porta a uno stato dello schermo errato o a errori di caricamento. Esaminiamo quattro casi principali in cui la navigazione differita è necessaria.

Autorizzazione e autenticazione

Se un utente tocca un contenuto protetto, l'applicazione deve prima verificare il token di accesso. La navigazione diretta allo schermo del contenuto risulterà in uno schermo vuoto o un errore 401 se il token è scaduto. La navigazione differita verifica il token, e solo in caso di successo — naviga allo schermo di destinazione. In caso di fallimento — reindirizza alla schermata di login.

Gestione dei Deeplink

Quando un'applicazione viene aperta tramite un link esterno, deve prima caricare lo schermo principale, ripristinare lo stato di navigazione e solo poi eseguire la transizione Deeplink. La navigazione diretta allo schermo di destinazione senza contesto principale porterà ad anomalie: uno stack di navigazione vuoto o uno stack indietro rotto.

Notifiche push con contenuto

Toccando una notifica push, l'applicazione può trovarsi in uno di tre stati: chiusa, in background o attiva. Deferred Navigation determina lo stato dell'applicazione, carica il contenuto necessario e solo poi mostra lo schermo di destinazione. iOS permette di gestire questo scenario tramite UNNotificationContentExtension.

Feature flag dinamico

Se la funzionalità di uno schermo è controllata da un feature flag dal server, la navigazione differita permette di richiedere prima la configurazione e solo poi mostrare lo schermo. Se la funzionalità è disabilitata — l'utente vede contenuto alternativo o un placeholder invece di uno schermo vuoto.

ScenarioNavigazione direttaDeferred Navigation
AutorizzazioneSchermo vuoto con token scadutoReindirizzamento al login
DeeplinkStack indietro rottoStack di navigazione corretto
PushCaricamento senza contestoDati pronti prima della transizione
Feature flagMostrare funzionalità non disponibilePlaceholder o alternativa

Deferred Navigation vs navigazione diretta

La navigazione diretta è un approccio tradizionale in cui la transizione viene eseguita immediatamente in risposta a un evento. L'utente preme un pulsante e il router dell'UI cambia immediatamente schermo. Questo approccio è semplice e prevedibile ma limitato in scenari che richiedono dati dal server o verifica di condizioni.

Deferred Navigation aggiunge un livello sotto forma di stato asincrono. Un evento utente avvia un'operazione, e una sottoscrizione al risultato controlla la navigazione. Questo aumenta la complessità del codice ma offre flessibilità: lo stesso trigger può portare a schermi diversi a seconda dei dati caricati.

La scelta tra i due approcci dipende dai requisiti: se mostrare uno schermo non richiede dati asincroni — usa la navigazione diretta. Se lo schermo dipende dal risultato di una richiesta, autorizzazione o condizioni esterne — la navigazione differita è necessaria. Un approccio ibrido, dove alcune transizioni sono dirette e altre differite, è la pratica più comune nelle applicazioni industriali.

Implementazione su Android: Navigation Component e ViewModel

Android Jetpack fornisce meccanismi per implementare Deferred Navigation a livello architetturale. L'idea principale è che la ViewModel gestisce lo stato, mentre l'Activity o il Fragment si sottoscrive ai cambiamenti e attiva la navigazione tramite NavController.

Deferred Navigation con StateFlow

StateFlow nelle coroutine Kotlin è lo strumento ideale per la navigazione differita. La ViewModel aggiorna uno StateFlow con un evento di navigazione, e l'Activity lo osserva ed esegue la transizione. Una volta che l'evento è stato elaborato, lo StateFlow viene pulito, prevenendo la navigazione ripetuta.

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 con NavController

Nell'Activity, una sottoscrizione alla navigazione attiva NavController con il percorso dalla ViewModel. Per prevenire la navigazione ripetuta durante la rotazione dello schermo, viene utilizzato un wrapper NavigationEventWrapper che elabora l'evento solo una volta. Jetpack Navigation 2.7+ supporta Safe Args per il passaggio di argomenti type-safe.

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)
                }
            }
        }
    }
}

Implementazione su iOS: Coordinator Pattern e Combine

iOS non ha un Navigation Component integrato simile ad Android Jetpack, quindi gli sviluppatori implementano Deferred Navigation tramite il Coordinator Pattern in combinazione con Combine o async/await. Il Coordinator gestisce lo stack degli schermi e prende decisioni di navigazione basate sui dati caricati.

Coordinator con Combine

Coordinator è un oggetto che gestisce la navigazione tra ViewController. In combinazione con Combine, la ViewModel pubblica eventi tramite PassthroughSubject, e il Coordinator vi si sottoscrive ed esegue la transizione. Questo approccio separa completamente l'UI dalla logica di navigazione e segue le raccomandazioni di Apple per l'architettura delle applicazioni.

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 con async/await

Swift 5.5 ha introdotto la concorrenza strutturata, che permette di implementare la navigazione differita tramite async/await senza Combine. La ViewModel fornisce una funzione async che restituisce un percorso dopo il caricamento dei dati. Il Coordinator chiama questa funzione in un Task ed esegue la transizione basata sul percorso ricevuto.

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)
    }
}

Errori comuni e best practices

Deferred Navigation semplifica la gestione degli scenari asincroni ma richiede un approccio disciplinato alla gestione dello stato. Esaminiamo i principali errori che gli sviluppatori commettono nell'implementare la navigazione differita.

Errore: navigazione prima del completamento dell'inizializzazione

L'errore più comune è tentare di eseguire una navigazione differita prima che lo schermo principale sia completamente inizializzato e il NavController o Coordinator sia pronto per la transizione. Su Android questo porta a IllegalStateException, su iOS — a uno stato UI indefinito. La soluzione è assicurarsi che il ciclo di vita del componente sia nello stato STARTED o RESUMED prima di attivare la navigazione.

Errore: navigazione ripetuta al ritorno su uno schermo

Se lo StateFlow o Subject non pulisce l'evento dopo l'elaborazione, al ritorno sullo schermo precedente l'utente può essere automaticamente reindirizzato allo stesso schermo. Usa SharedFlow con replay=0 su Android o CurrentValueSubject con nil dopo l'elaborazione su iOS, in modo che l'evento di navigazione si attivi solo una volta.

Best Practice: un unico NavController o Coordinator

Usa un componente centrale per tutta la navigazione nell'applicazione. Quando ogni Activity, Fragment o ViewController ha il proprio controller di navigazione, la navigazione differita tra diverse parti dell'applicazione diventa caotica. Un unico Coordinator semplifica il debugging e il test degli scenari di navigazione.

Best Practice: test degli scenari di navigazione differita

Deferred Navigation è più difficile da testare della navigazione diretta perché le operazioni asincrone introducono un fattore tempo. Usa TestDispatcher su Android (kotlinx-coroutines-test) e XCTestExpectation su iOS per simulare il caricamento dei dati e verificare che la navigazione segua il percorso atteso. Mocka i servizi di autorizzazione e deeplink per test isolati di ogni scenario.

Domande frequenti

In cosa Deferred Navigation si differenzia da Deep Link?

Deferred Navigation è un pattern di transizione differita che può essere applicato in qualsiasi scenario asincrono. Deep Link è un trigger per la navigazione differita, ma non l'unico. Anche l'autenticazione e i feature flag utilizzano la navigazione differita.

Si possono combinare navigazione differita e diretta?

, la maggior parte delle applicazioni utilizza un approccio ibrido. Uno schermo di elenco prodotti (senza dipendenze asincrone) può usare la navigazione diretta, mentre uno schermo di dettagli con caricamento dati usa quella differita. La separazione è determinata dall'architettura di ogni specifico schermo.

Come evitare race condition con la navigazione differita?

Usa SharedFlow senza replay su Android e combineLatest senza buffer su iOS. Cancella le sottoscrizioni precedenti a un nuovo trigger. Questo garantisce che solo l'ultimo evento di navigazione venga elaborato.

Jetpack Compose supporta la navigazione differita?

, in Compose la navigazione differita viene implementata tramite sottoscrizione al StateFlow della ViewModel e chiamata di NavController.navigate in LaunchedEffect. Google raccomanda di usare Navigation Compose con un modello event-driven per scenari differiti.

Come gestire la navigazione differita quando l'app è minimizzata?

Rimanda la navigazione fino a quando l'applicazione diventa nuovamente attiva. Su Android usa Lifecycle.State.STARTED per filtrare gli eventi. Su iOS verifica UIApplication.State nei blocchi Combine o async/await.

Riepilogo

  • Deferred Navigation — un pattern in cui la transizione avviene dopo il completamento di un'operazione asincrona
  • Scenari principali: autenticazione, Deeplink, notifiche push, feature flag
  • StateFlow/SharedFlow su Android e Combine/async-await su iOS sono strumenti di implementazione
  • Deferred Navigation a differenza della diretta non causa race condition durante il caricamento asincrono
  • Navigation Component su Android e Coordinator Pattern su iOS sono le architetture di base
  • Un unico NavController/Coordinator è obbligatorio per una navigazione consistente
  • Il test della navigazione differita richiede TestDispatcher e mock per servizi asincroni

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche