Deferred Navigation ist ein Muster der verzögerten Navigation, bei dem der Übergang zum nächsten Bildschirm nach Abschluss einer asynchronen Operation erfolgt, nicht direkt im Moment der Benutzeraktion. Laut Android Developers (2024) hilft die verzögerte Navigation, Wettlaufsituationen zwischen Navigation und Datenladen zu vermeiden und vereinfacht die Handhabung von Übergängen aus Push-Benachrichtigungen und Deeplinks. Der Hauptunterschied — die Route wird berechnet, nachdem alle erforderlichen Daten verfügbar sind.
Wichtige Punkte
Deferred Navigation ist ein architektonisches Muster, bei dem die Navigationsentscheidung verschoben wird, bis alle erforderlichen Daten verfügbar sind. Im Gegensatz zu einem direkten Übergang, bei dem der Benutzer eine Taste drückt und sofort zu einem neuen Bildschirm gelangt, trennt die verzögerte Navigation das Auslöseereignis und den tatsächlichen Übergang, indem sie eine asynchrone Operation dazwischen schaltet.
Architektonisch basiert Deferred Navigation auf Zustandsänderung: Ein Tastendruck initiiert einen asynchronen Prozess, und ein Abonnement seines Ergebnisses löst die Navigation aus. Dies ist besonders wichtig in Anwendungen mit MVVM- oder MVI-Architektur, bei denen die ViewModel den Zustand verwaltet und die View (Activity, Fragment, SwiftUI View) Änderungen abonniert und mit einem Übergang reagiert. Dieser Ansatz beseitigt die direkte Abhängigkeit zwischen UI und Navigationslogik.
Laut Google I/O 2023 wird die verzögerte Navigation für alle Szenarien empfohlen, in denen die Navigation vom Ergebnis einer Netzwerkanfrage, Authentifizierungsprüfung, Konfigurationsladung oder Berechtigungen abhängt. Das Muster ist auch bei der Verarbeitung von Deeplinks obligatorisch, bei denen die App zuerst starten, den Startbildschirm laden und erst dann zur Zielroute navigieren muss.
Deferred Navigation wird in Szenarien verwendet, in denen eine direkte Navigation zu einem falschen Bildschirmzustand oder Ladefehlern führt. Betrachten wir vier Hauptfälle, in denen eine verzögerte Navigation erforderlich ist.
Wenn ein Benutzer auf geschützte Inhalte tippt, muss die Anwendung zuerst das Zugriffstoken überprüfen. Direkte Navigation zum Inhaltsbildschirm führt zu einem leeren Bildschirm oder einem 401-Fehler, wenn das Token abgelaufen ist. Die verzögerte Navigation überprüft das Token und navigiert nur bei Erfolg zum Zielbildschirm. Bei Misserfolg wird zum Anmeldebildschirm umgeleitet.
Wenn eine Anwendung über einen externen Link geöffnet wird, muss sie zuerst den Startbildschirm laden, den Navigationszustand wiederherstellen und erst dann den Deeplink-Übergang ausführen. Eine direkte Navigation zum Zielbildschirm ohne Startkontext führt zu Anomalien: einem leeren Navigationsstapel oder einem defekten Rückwärtsstapel.
Beim Tippen auf eine Push-Benachrichtigung kann sich die Anwendung in einem von drei Zuständen befinden: geschlossen, im Hintergrund oder aktiv. Deferred Navigation bestimmt den Anwendungszustand, lädt den erforderlichen Inhalt und zeigt erst dann den Zielbildschirm an. iOS ermöglicht die Verarbeitung dieses Szenarios über UNNotificationContentExtension.
Wenn die Funktionalität eines Bildschirms durch ein Feature-Flag vom Server gesteuert wird, ermöglicht die verzögerte Navigation, zuerst die Konfiguration anzufordern und erst dann den Bildschirm anzuzeigen. Wenn die Funktion deaktiviert ist — sieht der Benutzer alternative Inhalte oder einen Platzhalter anstelle eines leeren Bildschirms.
| Szenario | Direkte Navigation | Deferred Navigation |
|---|---|---|
| Autorisierung | Leerer Bildschirm bei abgelaufenem Token | Umleitung zum Login |
| Deeplink | Defekter Rückwärtsstapel | Korrekter Navigationsstapel |
| Push | Laden ohne Kontext | Daten bereit vor dem Übergang |
| Feature-Flag | Anzeige nicht verfügbarer Funktion | Platzhalter oder Alternative |
Direkte Navigation ist ein traditioneller Ansatz, bei dem der Übergang sofort als Reaktion auf ein Ereignis ausgeführt wird. Der Benutzer drückt eine Taste und der UI-Router wechselt sofort den Bildschirm. Dieser Ansatz ist einfach und vorhersagbar, aber in Szenarien, die Daten vom Server oder Bedingungsprüfungen erfordern, eingeschränkt.
Deferred Navigation fügt eine Schicht in Form eines asynchronen Zustands hinzu. Ein Benutzerereignis startet eine Operation, und ein Abonnement des Ergebnisses steuert die Navigation. Dies erhöht die Codekomplexität, bietet aber Flexibilität: Derselbe Auslöser kann je nach geladenen Daten zu verschiedenen Bildschirmen führen.
Die Wahl zwischen den beiden Ansätzen hängt von den Anforderungen ab: Wenn für die Anzeige eines Bildschirms keine asynchronen Daten erforderlich sind — verwenden Sie die direkte Navigation. Wenn der Bildschirm vom Ergebnis einer Anfrage, Autorisierung oder externen Bedingungen abhängt — ist eine verzögerte Navigation erforderlich. Ein hybrider Ansatz, bei dem einige Übergänge direkt und einige verzögert sind, ist in industriellen Anwendungen die gängigste Praxis.
Android Jetpack bietet Mechanismen zur Implementierung von Deferred Navigation auf Architekturebene. Die Grundidee ist, dass die ViewModel den Zustand verwaltet, während die Activity oder Fragment Änderungen abonniert und die Navigation über NavController auslöst.
StateFlow in Kotlin-Koroutinen ist das ideale Werkzeug für verzögerte Navigation. Die ViewModel aktualisiert einen StateFlow mit einem Navigationsereignis, und die Activity beobachtet es und führt den Übergang aus. Nach der Verarbeitung des Ereignisses wird der StateFlow gelöscht, um eine wiederholte Navigation zu verhindern.
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))
}
}
}
In der Activity löst ein Abonnement der Navigation den NavController mit der Route aus der ViewModel aus. Um wiederholte Navigation bei Bildschirmdrehung zu verhindern, wird ein NavigationEventWrapper verwendet, der das Ereignis nur einmal verarbeitet. Jetpack Navigation 2.7+ unterstützt Safe Args für typsichere Argumentübergabe.
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 hat keine eingebaute Navigation Component ähnlich wie Android Jetpack, daher implementieren Entwickler Deferred Navigation durch das Coordinator Pattern in Kombination mit Combine oder async/await. Der Coordinator verwaltet den Bildschirmstapel und trifft Navigationsentscheidungen basierend auf geladenen Daten.
Coordinator ist ein Objekt, das die Navigation zwischen ViewControllern verwaltet. In Kombination mit Combine veröffentlicht die ViewModel Ereignisse über PassthroughSubject, und der Coordinator abonniert sie und führt den Übergang aus. Dieser Ansatz trennt die UI vollständig von der Navigationslogik und folgt den Empfehlungen von Apple für die Anwendungsarchitektur.
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 führte strukturierte Nebenläufigkeit ein, die es ermöglicht, verzögerte Navigation über async/await ohne Combine zu implementieren. Die ViewModel stellt eine async-Funktion bereit, die nach dem Laden von Daten eine Route zurückgibt. Der Coordinator ruft diese Funktion in einem Task auf und führt den Übergang basierend auf der empfangenen Route aus.
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)
}
}
Deferred Navigation vereinfacht die Handhabung asynchroner Szenarien, erfordert jedoch einen disziplinierten Ansatz bei der Zustandsverwaltung. Betrachten wir die häufigsten Fehler, die Entwickler bei der Implementierung der verzögerten Navigation machen.
Der häufigste Fehler ist der Versuch, Deferred Navigation durchzuführen, bevor der Startbildschirm vollständig initialisiert und der NavController oder Coordinator für den Übergang bereit ist. In Android führt dies zu IllegalStateException, in iOS — zu einem undefinierten UI-Zustand. Die Lösung besteht darin, sicherzustellen, dass sich der Lebenszyklus der Komponente im Zustand STARTED oder RESUMED befindet, bevor die Navigation ausgelöst wird.
Wenn der StateFlow oder Subject das Ereignis nach der Verarbeitung nicht löscht, kann der Benutzer bei der Rückkehr zum vorherigen Bildschirm automatisch auf denselben Bildschirm umgeleitet werden. Verwenden Sie SharedFlow mit replay=0 in Android oder CurrentValueSubject mit nil nach der Verarbeitung in iOS, damit das Navigationsereignis nur einmal ausgelöst wird.
Verwenden Sie eine zentrale Komponente für die gesamte Navigation in der Anwendung. Wenn jede Activity, jeder Fragment oder ViewController einen eigenen Navigationscontroller hat, wird die verzögerte Navigation zwischen verschiedenen Teilen der Anwendung chaotisch. Ein einziger Coordinator vereinfacht das Debuggen und Testen von Navigationsszenarien.
Deferred Navigation ist schwieriger zu testen als die direkte Navigation, da asynchrone Operationen einen Zeitfaktor einführen. Verwenden Sie TestDispatcher in Android (kotlinx-coroutines-test) und XCTestExpectation in iOS, um das Laden von Daten zu simulieren und zu überprüfen, ob die Navigation der erwarteten Route folgt. Mocken Sie Autorisierungs- und Deeplink-Dienste für isolierte Tests jedes Szenarios.
Häufig gestellte Fragen
Deferred Navigation ist ein Muster für verzögerte Übergänge, das in jedem asynchronen Szenario angewendet werden kann. Deep Link ist ein Auslöser für die verzögerte Navigation, aber nicht der einzige. Auch Authentifizierung und Feature-Flags verwenden verzögerte Navigation.
Ja, die meisten Anwendungen verwenden einen hybriden Ansatz. Ein Produktlistenbildschirm (ohne asynchrone Abhängigkeiten) kann direkte Navigation verwenden, während ein Detailbildschirm mit Datenladen verzögerte Navigation verwendet. Die Trennung wird durch die Architektur des jeweiligen Bildschirms bestimmt.
Verwenden Sie SharedFlow ohne replay in Android und combineLatest ohne Pufferung in iOS. Brechen Sie vorherige Abonnements bei einem neuen Auslöser ab. Dies stellt sicher, dass nur das letzte Navigationsereignis verarbeitet wird.
Ja, in Compose wird die verzögerte Navigation durch Abonnement des ViewModel-StateFlow und Aufruf von NavController.navigate in LaunchedEffect implementiert. Google empfiehlt die Verwendung von Navigation Compose mit einem ereignisgesteuerten Modell für verzögerte Szenarien.
Verschieben Sie die Navigation, bis die Anwendung wieder aktiv wird. In Android verwenden Sie Lifecycle.State.STARTED, um Ereignisse zu filtern. In iOS überprüfen Sie UIApplication.State in Combine- oder async/await-Blöcken.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch