Wie der Navigationszustand wiederhergestellt wird und wo bewusste Grenzen nötig sind
Wird eine React-Native-App per Force-Quit beendet und danach erneut geöffnet, geht standardmäßig der gesamte Navigationszustand verloren, weil sowohl React Navigation als auch Expo Router den aktuellen Screen und die Stack-Historie ausschließlich im Arbeitsspeicher halten. Für viele Apps ist das ein spürbarer Bruch im Nutzungserlebnis, wenn eine tief verschachtelte Ansicht nach jedem Neustart wieder beim Start-Screen beginnt. Dieser Artikel zeigt, wie sich der Navigationszustand gezielt persistieren lässt, welche Konfiguration dafür in React Navigation und Expo Router nötig ist, und wo diese Wiederherstellung bewusst unterbleiben sollte, etwa nach einem abgelaufenen Login.
Inhaltsverzeichnis
- 1. Warum der Navigationszustand standardmäßig verloren geht
- 2. Die State-Persistence-API von React Navigation
- 3. Konkrete Implementierung Schritt für Schritt
- 4. Der Ansatz von Expo Router: URL statt Zustandsobjekt
- 5. Versionierung des gespeicherten Zustands nicht vergessen
- 6. Wann der Zustand bewusst nicht wiederhergestellt werden sollte
- 7. Selektive Persistenz: einzelne Screens gezielt ausschließen
- 8. Kombination mit Deep-Linking-Priorität beim Kaltstart
- 9. Testing-Strategie für Force-Quit-Szenarien
- 10. Zusammenfassung
- 11. FAQ
1. Warum der Navigationszustand standardmäßig verloren geht
Sowohl React Navigation als auch Expo Router verwalten den aktuellen Navigationszustand, also welcher Screen aktiv ist und welche Stack-Historie dahinter liegt, als reines JavaScript-Objekt im Arbeitsspeicher der laufenden App-Instanz. Wird der native Prozess beendet, sei es durch einen manuellen Force-Quit, durch das Betriebssystem im Rahmen des Speichermanagements oder durch einen Absturz, verschwindet dieses Objekt vollständig zusammen mit dem restlichen Laufzeitzustand der App. Beim nächsten Start wird die Navigation vollständig neu initialisiert und beginnt beim konfigurierten Startbildschirm, unabhängig davon, wo sich die Nutzerin zuvor befand.
Dieses Verhalten unterscheidet sich fundamental von nativen iOS- und Android-Apps, bei denen das Betriebssystem den UI-Zustand einzelner Ansichten in bestimmten Fällen über einen Prozessneustart hinweg wiederherstellen kann. React Native bietet diese Wiederherstellung nicht automatisch an, weil der gesamte Navigationsbaum als flexible, dynamisch erzeugte Datenstruktur im JavaScript-Bereich existiert und keine direkte Entsprechung im nativen View-Controller- beziehungsweise Activity-Zustand hat, den das Betriebssystem selbstständig sichern könnte.
2. Die State-Persistence-API von React Navigation
React Navigation stellt für genau dieses Problem eine eingebaute Persistence-API bereit, die über die Props onStateChange und initialState am NavigationContainer angebunden wird. Bei jeder Zustandsänderung, also bei jeder Navigation, meldet onStateChange den vollständigen neuen Zustand als serialisierbares Objekt, das anschließend in einem persistenten Speicher wie AsyncStorage abgelegt werden kann. Beim nächsten App-Start liest die Anwendung diesen gespeicherten Zustand vor dem ersten Rendern des NavigationContainer aus und übergibt ihn über initialState, wodurch React Navigation exakt an der zuvor gespeicherten Stelle weiterarbeitet, statt bei der konfigurierten Startroute zu beginnen.
Weil das Auslesen aus AsyncStorage asynchron erfolgt, muss der eigentliche NavigationContainer so lange zurückgehalten werden, bis der gespeicherte Zustand tatsächlich geladen wurde, üblicherweise über einen einfachen isReady-Flag im Komponentenzustand. Ohne diese Wartezeit würde der Container bereits mit dem Standardzustand initialisiert und der später eintreffende gespeicherte Zustand hätte keine Wirkung mehr, weil initialState ausschließlich beim allerersten Rendern ausgewertet wird.
import { useState, useCallback } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { NavigationContainer, InitialState } from '@react-navigation/native';
const PERSISTENCE_KEY = 'NAVIGATION_STATE_V3';
export default function AppNavigation() {
const [isReady, setIsReady] = useState(false);
const [initialState, setInitialState] = useState<InitialState>();
const restoreState = useCallback(async () => {
const savedStateString = await AsyncStorage.getItem(PERSISTENCE_KEY);
if (savedStateString) {
setInitialState(JSON.parse(savedStateString));
}
setIsReady(true);
}, []);
if (!isReady) {
return null;
}
return (
<NavigationContainer
initialState={initialState}
onStateChange={(state) =>
AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state))
}
>
{/* Navigator hier */}
</NavigationContainer>
);
}
3. Konkrete Implementierung Schritt für Schritt
In der Praxis lohnt es sich, das Laden des gespeicherten Zustands mit dem generellen App-Startvorgang zu verbinden, etwa mit dem Ausblenden eines Splash-Screens über expo-splash-screen, sodass die Nutzerin während des kurzen Ladevorgangs keinen leeren Bildschirm sieht. Der restoreState-Aufruf sollte dabei in einem try-catch-Block abgesichert werden, weil ein beschädigter oder aus einer inkompatiblen App-Version stammender gespeicherter Zustand sonst zu einem unbehandelten Fehler führen kann, der den App-Start blockiert.
Zusätzlich empfiehlt es sich, das Schreiben in AsyncStorage zu drosseln, statt bei jeder einzelnen Navigation sofort zu schreiben, etwa über ein einfaches Debouncing mit 300 bis 500 Millisekunden Verzögerung, weil sehr häufige, schnell aufeinanderfolgende Navigationen sonst unnötig viele Schreibvorgänge auf den persistenten Speicher auslösen und dadurch spürbar Leistung kosten können, insbesondere auf älteren Android-Geräten mit langsamerem internen Speicher.
4. Der Ansatz von Expo Router: URL statt Zustandsobjekt
Expo Router verfolgt einen konzeptionell anderen Ansatz als die klassische React-Navigation-Persistence-API, weil jede Route dort ohnehin bereits einer URL entspricht und der Navigationszustand sich zu einem großen Teil direkt aus dem aktuellen Pfad ableiten lässt. Statt eines komplexen, verschachtelten Zustandsobjekts genügt es in vielen Fällen, lediglich den zuletzt besuchten Pfad zu speichern und die App beim Neustart über router.replace direkt auf diesen Pfad zu navigieren, wodurch Expo Router die passende Stack-Historie größtenteils selbst rekonstruiert.
Für Fälle, in denen auch die vollständige Stack-Historie erhalten bleiben soll, etwa mehrere aufeinander aufgebaute Push-Schritte innerhalb eines Tabs, lässt sich weiterhin dieselbe onStateChange- und initialState-Mechanik von React Navigation nutzen, da Expo Router intern auf React Navigation aufbaut und denselben NavigationContainer verwendet. In der Praxis reicht für die meisten Apps jedoch die einfachere, URL-basierte Wiederherstellung des letzten Pfades völlig aus und vermeidet gleichzeitig die Komplexität einer vollständigen Zustandsserialisierung.
5. Versionierung des gespeicherten Zustands nicht vergessen
Ein gespeicherter Navigationszustand referenziert konkrete Routennamen und Screen-Parameter, die sich mit jedem App-Update ändern können, etwa wenn ein Screen umbenannt, ein Parameter umstrukturiert oder eine ganze Route entfernt wird. Wird ein solcher veralteter Zustand nach einem Update unverändert über initialState eingespielt, kann React Navigation versuchen, eine nicht mehr existierende Route zu rendern, was im besten Fall zu einem Absturz und im schlechteren Fall zu einem stillen, verwirrenden Fehlzustand führt.
Die zuverlässigste Absicherung dagegen ist ein Versionssuffix im AsyncStorage-Schlüssel selbst, wie im obigen Beispiel mit NAVIGATION_STATE_V3 gezeigt, das bei jeder strukturellen Änderung an der Navigationsstruktur bewusst hochgezählt wird. Ändert sich der Schlüssel, findet die App beim nächsten Start keinen gespeicherten Zustand mehr unter dem neuen Namen, verwirft den veralteten Eintrag stillschweigend und startet stattdessen sauber bei der konfigurierten Startroute, ohne dass ein Abgleich der Struktur programmiert werden müsste.
6. Wann der Zustand bewusst nicht wiederhergestellt werden sollte
Nicht jede Situation profitiert von einer Wiederherstellung des Navigationszustands, und ein Login-Screen ist das klarste Beispiel dafür: Ist die gespeicherte Session beim Neustart abgelaufen, wäre es fehlerhaft, die Nutzerin zunächst auf einen tief verschachtelten, eigentlich geschützten Screen zurückzuführen, nur um sie von dort sofort wieder zum Login umzuleiten. Ein Auth-Guard, der vor jeder Zustandswiederherstellung zuerst den aktuellen Session-Status prüft und bei fehlender Authentifizierung den gespeicherten Zustand vollständig verwirft, verhindert dieses kurze, aber verwirrende Aufblitzen geschützter Inhalte.
Ähnlich problematisch sind einmalige oder zeitkritische Flows wie ein Zahlungsvorgang, ein mehrstufiges Onboarding oder ein Bestätigungsdialog nach einer bereits abgeschlossenen Aktion, bei denen eine Wiederherstellung mitten im Prozess zu inkonsistenten oder doppelten Aktionen führen könnte. Für solche Screens empfiehlt es sich, sie explizit von der Persistenz auszuschließen, etwa indem der Speicherzustand vor dem Betreten dieser Screens bewusst nicht aktualisiert oder beim Verlassen des Flows aktiv gelöscht wird.
7. Selektive Persistenz: einzelne Screens gezielt ausschließen
Statt den gesamten Navigationszustand pauschal zu speichern, lässt sich der onStateChange-Handler so erweitern, dass er den empfangenen Zustand vor dem Schreiben inspiziert und bestimmte Routennamen gezielt herausfiltert oder durch eine sichere Fallback-Route ersetzt. Enthält der aktuelle Zustand beispielsweise eine Route namens Checkout oder PaymentConfirmation, wird statt des tatsächlichen Zustands ein reduzierter Zustand mit der übergeordneten, unkritischen Route gespeichert, sodass ein Neustart während eines Zahlungsvorgangs nicht versucht, genau diesen Vorgang unvollständig fortzusetzen.
Dieselbe Filterlogik eignet sich auch für Modal-Screens mit kurzer Lebensdauer, etwa einen einmaligen Berechtigungs-Dialog oder eine Bewertungsaufforderung, die nach einem Neustart keinen Sinn mehr ergeben, weil ihr ursprünglicher Auslöser nicht mehr im aktuellen Kontext existiert. Eine klare, dokumentierte Liste der von der Persistenz ausgeschlossenen Routen im Code selbst hilft dem gesamten Team, bei neuen Screens bewusst zu entscheiden, ob sie zur wiederherstellbaren oder zur bewusst flüchtigen Kategorie gehören.
8. Kombination mit Deep-Linking-Priorität beim Kaltstart
Wird eine App nicht einfach neu geöffnet, sondern über einen Deep Link gestartet, etwa aus einer Push-Benachrichtigung oder einem geteilten Produktlink heraus, sollte dieser Deep Link grundsätzlich Vorrang vor dem gespeicherten Navigationszustand haben, weil er die aktuellere, bewusste Nutzerabsicht ausdrückt. Die praktische Lösung besteht darin, beim App-Start zuerst zu prüfen, ob eine Linking-URL vorhanden ist, und den gespeicherten Zustand nur dann über initialState einzuspielen, wenn kein solcher Link vorliegt.
Diese Priorisierung verhindert eine verwirrende Situation, in der ein Nutzer auf eine Benachrichtigung tippt, aber wegen eines wiederhergestellten alten Navigationszustands auf einem völlig anderen Screen landet als dem, den die Benachrichtigung eigentlich adressiert hat. In der Praxis lässt sich diese Prüfung sauber in derselben restoreState-Funktion unterbringen, die auch den AsyncStorage-Zustand lädt, indem Linking.getInitialURL vor dem eigentlichen State-Restore ausgewertet wird.
9. Testing-Strategie für Force-Quit-Szenarien
Navigation-State-Persistenz lässt sich kaum sinnvoll durch klassische Komponententests abdecken, weil der eigentliche Effekt erst über einen echten Prozessneustart sichtbar wird, weshalb manuelles Testen auf physischen Geräten und Simulatoren ein fester Bestandteil des Test-Ablaufs bleiben sollte. Ein wiederholbarer manueller Testfall navigiert bewusst mehrere Ebenen tief in die App hinein, beendet den Prozess vollständig über das App-Umschalter-Menü und öffnet die App danach erneut, um zu prüfen, ob exakt derselbe Screen mit denselben Parametern wieder erscheint.
Zusätzlich sollte gezielt der Fall eines abgelaufenen Logins während eines Force-Quit getestet werden, indem die Session serverseitig oder über einen manuell manipulierten Token vorab ungültig gemacht wird, um sicherzustellen, dass der Auth-Guard den gespeicherten Zustand tatsächlich verwirft, statt einen geschützten Screen kurz aufblitzen zu lassen. Für automatisierte End-to-End-Tests mit Detox oder Maestro lässt sich ein App-Neustart-Schritt zusätzlich in die Testsuite aufnehmen, um zumindest den grundsätzlichen Wiederherstellungsmechanismus regelmäßig gegen Regressionen abzusichern.
| Ansatz | Speicherort | Granularität | Typischer Einsatz |
|---|---|---|---|
| React Navigation Persistence API | AsyncStorage | Vollständiger, verschachtelter Zustand | Klassische Stack-/Tab-Apps mit React Navigation |
| Expo Router URL-Persistenz | AsyncStorage (nur Pfad) | Letzter besuchter Pfad | Dateibasiertes Routing, einfache Wiederherstellung |
| Selektive Filterung | AsyncStorage mit Vorverarbeitung | Bestimmte Routen ausgeschlossen | Checkout, Zahlungs- und Onboarding-Flows |
| Versionierter Schlüssel | AsyncStorage mit Schlüsselsuffix | Ganzer gespeicherter Zustand | Absicherung gegen strukturelle App-Updates |
| Deep-Link-Priorität | Kein zusätzlicher Speicher | Einzelner Zielpfad | Push-Benachrichtigungen, geteilte Links |
Mironsoft
React-Native-App-Entwicklung und Magento-Anbindung
Eine mobile App zum Magento-Shop, die wirklich rund läuft?
Wir entwickeln React-Native-Apps, die sauber an die Magento REST- oder GraphQL-API angebunden sind, von der ersten Codezeile bis zur Veröffentlichung im App Store und bei Google Play.
App-Konzeption
Architektur und Feature-Umfang einer Magento-angebundenen App gemeinsam planen.
Magento-API-Integration
Produktkatalog, Warenkorb und Checkout sauber an die Shop-API anbinden.
Store-Veröffentlichung
App Store- und Google-Play-Freigabeprozess ohne Stolperfallen begleiten.
10. Zusammenfassung
Navigation-State-Persistenz: Das Wichtigste auf einen Blick
Grundmechanismus
onStateChange schreibt den Navigationszustand, initialState liest ihn beim nächsten Start vor dem ersten Rendern wieder ein.
Expo Router
Nutzt statt eines komplexen Zustandsobjekts meist nur den zuletzt besuchten Pfad und rekonstruiert die Historie daraus.
Grenzen
Login-Screens, Zahlungs- und Onboarding-Flows sollten bewusst von der Wiederherstellung ausgeschlossen werden.
Priorität
Ein Deep Link beim Kaltstart sollte immer Vorrang vor einem wiederhergestellten alten Navigationszustand haben.