Deferred Navigation — wat is het, principes en implementatie

Auteur: IT Sectr Gepubliceerd: 2026-06-10 Leestijd: 9 min

Deferred Navigation is een patroon van uitgestelde navigatie waarbij de overgang naar het volgende scherm plaatsvindt na het voltooien van een asynchrone bewerking, niet direct op het moment van de gebruikersactie. Volgens Android Developers (2024) helpt deferred navigation om race conditions tussen navigatie en gegevens laden te voorkomen, en vereenvoudigt het de afhandeling van overgangen vanuit pushmeldingen en Deeplink. Het belangrijkste verschil — de route wordt berekend nadat alle benodigde gegevens beschikbaar zijn.

Belangrijkste punten

  • Deferred Navigation — uitgestelde navigatie, waarbij de overgang wordt uitgevoerd na het voltooien van asynchrone bewerkingen
  • Directe navigatie verwerkt de overgang onmiddellijk, deferred — wacht op gegevens
  • Typische scenario's: autorisatie, Deeplink, pushmeldingen, configuratie laden
  • In Android geïmplementeerd via Navigation Component met callbacks en StateFlow
  • In iOS wordt Combine of async/await met een navigatiecoördinator gebruikt

Wat is Deferred Navigation

Deferred Navigation is een architectuurpatroon waarbij de navigatiebeslissing wordt uitgesteld tot het moment dat alle benodigde gegevens beschikbaar zijn. In tegenstelling tot een directe overgang, waarbij de gebruiker op een knop drukt en meteen op een nieuw scherm komt, scheidt deferred navigation de triggergebeurtenis en de daadwerkelijke overgang door er een asynchrone bewerking tussen te plaatsen.

Architectonisch is Deferred Navigation gebaseerd op toestandsverandering: het indrukken van een knop start een asynchroon proces, en het abonneren op het resultaat activeert de navigatie. Dit is vooral belangrijk in apps met MVVM- of MVI-architectuur, waarbij ViewModel de toestand beheert en View (Activity, Fragment, SwiftUI View) zich abonneert op wijzigingen en reageert met een overgang. Deze aanpak elimineert de directe afhankelijkheid tussen UI en navigatielogica.

Volgens Google I/O 2023 wordt deferred navigation aanbevolen voor alle scenario's waarin navigatie afhankelijk is van het resultaat van een netwerkverzoek, autorisatiecontrole, het laden van configuratie of machtigingen. Het patroon is ook verplicht bij het verwerken van Deeplink, wanneer de app eerst moet opstarten, het hoofdscherm moet laden en pas daarna naar de doelroute moet gaan.

Wanneer is uitgestelde navigatie nodig

Deferred Navigation wordt toegepast in scenario's waar een directe overgang leidt tot een onjuiste schermtoestand of laadfouten. Laten we vier hoofdgevallen bekijken waarin deferred navigation verplicht is.

Autorisatie en authenticatie

Als een gebruiker op beveiligde inhoud klikt, moet de app eerst het toegangstoken controleren. Directe navigatie naar het inhoudsscherm leidt tot een leeg scherm of fout 401 als het token is verlopen. Deferred navigation controleert het token en gaat alleen bij succes naar het doelscherm. Bij mislukking wordt omgeleid naar het inlogscherm.

Deeplink verwerken

Wanneer de app wordt geopend via een externe link, moet eerst het hoofdscherm worden geladen, de navigatiestatus worden hersteld en pas daarna de overgang via Deeplink worden uitgevoerd. Directe navigatie naar het doelscherm zonder hoofdcontext leidt tot anomalieën: een lege navigatiestack of een beschadigde back stack.

Pushmeldingen met inhoud

Bij het tikken op een pushmelding kan de app zich in drie toestanden bevinden: gesloten, op de achtergrond of actief. Deferred Navigation bepaalt de toestand van de app, laadt de benodigde inhoud en toont pas daarna het doelscherm. iOS kan dit scenario verwerken via UNNotificationContentExtension.

Dynamische feature flag

Als de functionaliteit van een scherm wordt beheerd door een feature flag van de server, kan deferred navigation eerst de configuratie opvragen en dan pas het scherm tonen. Als de functie is uitgeschakeld, ziet de gebruiker alternatieve inhoud of een placeholder in plaats van een leeg scherm.

ScenarioDirecte navigatieDeferred Navigation
AutorisatieLeeg scherm bij verlopen tokenDoorverwijzing naar inloggen
DeeplinkBeschadigde back stackCorrecte navigatiestack
PushLaden zonder contextGegevens klaar voor overgang
Feature flagNiet-beschikbare functie tonenPlaceholder of alternatief

Deferred Navigation vs directe navigatie

Directe navigatie (direct navigation) is een traditionele aanpak waarbij de overgang onmiddellijk wordt uitgevoerd als reactie op een gebeurtenis. De gebruiker drukt op een knop en de UI-router schakelt meteen van scherm. Deze aanpak is eenvoudig en voorspelbaar, maar beperkt in scenario's waar gegevens van de server of voorwaardelijke controles nodig zijn.

Deferred Navigation voegt een laag toe in de vorm van een asynchrone toestand. De gebruikersgebeurtenis start een bewerking en het abonneren op het resultaat beheert de navigatie. Dit verhoogt de complexiteit van de code, maar biedt flexibiliteit: dezelfde trigger kan naar verschillende schermen leiden, afhankelijk van de geladen gegevens.

De keuze tussen twee benaderingen hangt af van de vereisten: als voor het weergeven van een scherm geen asynchrone gegevens nodig zijn, gebruik dan directe navigatie. Als het scherm afhankelijk is van het resultaat van een verzoek, autorisatie of externe omstandigheden, is deferred navigation verplicht. Een hybride aanpak, waarbij een deel van de overgangen direct is en een deel uitgesteld, is de meest voorkomende praktijk in productie-apps.

Implementatie in Android: Navigation Component en ViewModel

Android Jetpack biedt mechanismen voor het implementeren van Deferred Navigation op architectuurniveau. Het hoofdidee is dat ViewModel de toestand beheert en Activity of Fragment zich abonneert op wijzigingen en navigatie activeert via NavController.

Deferred Navigation met StateFlow

StateFlow in Kotlin coroutines is het ideale hulpmiddel voor deferred navigation. ViewModel werkt StateFlow bij met de navigatiegebeurtenis en Activity observeert deze en voert de overgang uit. Nadat de gebeurtenis is verwerkt, wordt StateFlow gewist, waardoor herhaalde navigatie wordt voorkomen.

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

In Activity activeert het abonneren op navigatie NavController met de route uit ViewModel. Om herhaalde navigatie bij schermrotatie te voorkomen, wordt een NavigationEventWrapper gebruikt die de gebeurtenis slechts één keer verwerkt. Jetpack Navigation 2.7+ ondersteunt Safe Args voor typeveilige argumentoverdracht.

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

Implementatie in iOS: Coordinator Pattern en Combine

iOS heeft geen ingebouwde Navigation Component zoals Android Jetpack, dus ontwikkelaars implementeren Deferred Navigation via Coordinator Pattern in combinatie met Combine of async/await. Coordinator beheert de schermstack en neemt navigatiebeslissingen op basis van geladen gegevens.

Coordinator met Combine

Coordinator is een object dat de navigatie tussen ViewControllers beheert. In combinatie met Combine publiceert ViewModel gebeurtenissen via PassthroughSubject en abonneert Coordinator zich erop en voert de overgang uit. Deze aanpak scheidt UI volledig van navigatielogica en voldoet aan de aanbevelingen van Apple voor app-architectuur.

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

Swift 5.5 introduceerde structured concurrency, waarmee uitgestelde navigatie via async/await zonder Combine kan worden geïmplementeerd. ViewModel biedt een async-functie die de route retourneert na het laden van gegevens. Coordinator roept deze functie aan in Task en voert de overgang uit volgens de ontvangen route.

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

Veelvoorkomende fouten en best practices

Deferred Navigation vereenvoudigt de verwerking van asynchrone scenario's, maar vereist een gedisciplineerde aanpak van toestandsbeheer. Laten we de belangrijkste fouten bekijken die ontwikkelaars maken bij het implementeren van deferred navigation.

Fout: navigatie vóór voltooiing van initialisatie

De meest voorkomende fout is het proberen uitvoeren van deferred navigation voordat het hoofdscherm volledig is geïnitialiseerd en NavController of Coordinator klaar is voor de overgang. In Android leidt dit tot IllegalStateException, in iOS tot een ongedefinieerde UI-toestand. Oplossing: zorg ervoor dat de levenscyclus van de component in de toestand STARTED of RESUMED is voordat navigatie wordt gestart.

Fout: herhaalde navigatie bij terugkeer naar scherm

Als StateFlow of Subject de gebeurtenis niet wist na verwerking, kan de gebruiker bij terugkeer naar het vorige scherm automatisch opnieuw naar hetzelfde scherm worden geleid. Gebruik SharedFlow met replay=0 op Android of CurrentValueSubject met nil na verwerking op iOS, zodat de navigatiegebeurtenis slechts één keer wordt geactiveerd.

Best Practice: één NavController of Coordinator

Gebruik één centrale component voor alle navigatie in de app. Wanneer elke Activity, Fragment of ViewController zijn eigen navigatiecontroller heeft, wordt deferred navigation tussen verschillende delen van de app chaotisch. Eén Coordinator vergemakkelijkt het debuggen en testen van navigatiescenario's.

Best Practice: testen van uitgestelde navigatiescenario's

Deferred Navigation is moeilijker te testen dan directe navigatie omdat asynchrone bewerkingen een tijdfactor introduceren. Gebruik TestDispatcher in Android (kotlinx-coroutines-test) en XCTestExpectation in iOS voor het simuleren van gegevens laden en controleer of navigatie volgens de verwachte route wordt uitgevoerd. Mock autorisatie- en deeplinkdiensten voor geïsoleerd testen van elk scenario.

Veelgestelde vragen

Waarin verschilt Deferred Navigation van Deep Link?

Deferred Navigation is een patroon voor uitgestelde overgangen dat in elk asynchroon scenario kan worden toegepast. Deep Link is een van de triggers voor deferred navigation, maar niet de enige. Autorisatie en feature flags gebruiken ook uitgestelde navigatie.

Kunnen deferred en direct navigation worden gecombineerd?

Ja, de meeste apps gebruiken een hybride aanpak. Een productlijstscherm (zonder asynchrone afhankelijkheden) kan directe navigatie gebruiken, terwijl een detailscherm met gegevens laden deferred gebruikt. De verdeling wordt bepaald door de architectuur van het specifieke scherm.

Hoe voorkom ik race conditions bij deferred navigation?

Gebruik SharedFlow zonder replay op Android en combineLatest zonder buffering op iOS. Annuleer eerdere abonnementen bij een nieuwe trigger. Dit garandeert dat alleen de laatste navigatiegebeurtenis wordt verwerkt.

Ondersteunt Jetpack Compose deferred navigation?

Ja, in Compose wordt deferred navigation geïmplementeerd via abonneren op de StateFlow van ViewModel en het aanroepen van NavController.navigate in LaunchedEffect. Google adviseert Navigation Compose met een gebeurtenismodel voor deferred scenario's.

Hoe ga ik om met deferred navigation bij een geminimaliseerde app?

Stel de navigatie uit tot de app weer actief wordt. In Android gebruik je Lifecycle.State.STARTED om gebeurtenissen te filteren. In iOS controleer je UIApplication.State in het Combine- of async/await-blok.

Samenvatting

  • Deferred Navigation — patroon waarbij de overgang wordt uitgevoerd na voltooiing van een asynchrone bewerking
  • Belangrijkste scenario's: autorisatie, Deeplink, pushmeldingen, feature flags
  • StateFlow/SharedFlow in Android en Combine/async-await in iOS — implementatiehulpmiddelen
  • Deferred Navigation veroorzaakt in tegenstelling tot directe navigatie geen race conditions bij asynchroon laden
  • Navigation Component in Android en Coordinator Pattern in iOS — basisarchitecturen
  • Eén NavController/Coordinator is verplicht voor consistente navigatie
  • Testen van deferred navigation vereist TestDispatcher en mocks voor asynchrone diensten

We ontwikkelen een mobiele applicatie turnkey

IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.

Bespreek het project

Lees ook