Das Phoenix Pattern in Flutter: App neu starten, ohne sie neu zu starten
Eine ca. 30-zeilige Technik ohne externe Abhängigkeiten, um eine Flutter-App programmatisch neu zu starten, indem der gesamte Widget Tree neu aufgebaut wird.
Das Phoenix Pattern in Flutter: App neu starten, ohne sie neu zu starten
Zuletzt aktualisiert: März 2026. Getestet mit Flutter 3.27+ und Dart 3.6+.
Dieser Beitrag setzt Vertrautheit mit Flutters Widget Tree, StatefulWidget und dem Konzept von Keys voraus. Falls du mit Keys noch nicht vertraut bist, starte mit der Flutter-Dokumentation zu Keys.
Das Phoenix Pattern ist eine Technik, um eine Flutter-App programmatisch neu zu starten, indem der gesamte Widget Tree mithilfe von Flutters Key-basierter Reconciliation von Grund auf neu aufgebaut wird.
Es reißt alle Widgets ab, disposed den gesamten State und erstellt alles neu — ohne den OS-Prozess zu beenden. Das Pattern braucht ca. 30 Zeilen Code und keine externen Abhängigkeiten.
TL;DR: Wrappe deine App in ein StatefulWidget, das einen UniqueKey hält. Ändere den Key, und Flutter zerstört und baut jeden Widget darunter neu auf. Sämtlicher Widget State ist weg. Alle Provider werden neu erstellt. Aber statische Variablen, Singletons und .env-Werte überleben — weil das Dart Isolate am Leben bleibt. Kenne diese Grenze, bevor du dich für Phoenix statt eines echten Prozess-Neustarts entscheidest.
Warum es das gibt
Du musst deine Flutter-App programmatisch neu starten. Der Nutzer hat sich ausgeloggt und du willst einen sauberen Zustand. Ein Initialisierungsschritt ist fehlgeschlagen und du willst es nochmal versuchen. Eine Konfigurationsänderung betrifft den gesamten Widget Tree.
Stand März 2026 hat Flutter keine eingebaute restartApp()-API. Es gibt einen offenen Feature Request (Issue #127409), der aber nicht umgesetzt wurde. In der Diskussion schlug Flutter-Contributor @feinstein vor, runApp() mit einem GlobalKey-Tausch zu wrappen — also das Phoenix Pattern ins Framework einzubauen — merkte aber an, dass das nur ein “Soft Restart” wäre, der den Widget State zurücksetzt, kein “Hard Restart”, der die Dart Engine neu startet. Statische Globals würden in beiden Fällen überleben.
Die Alternativen und ihre Probleme
| Methode | Plattform-Support | Restart-Level | Nachteil |
|---|---|---|---|
exit(0) | Android + iOS | OS-Prozess beenden | Apple rät davon ab — sieht aus wie ein Absturz, Risiko der App-Store-Ablehnung |
SystemNavigator.pop() | Nur Android | App schließen | Macht auf iOS nichts |
restart_app Package | Android + iOS | Nativer Restart | Unter iOS wird die App beendet und eine lokale Notification zum erneuten Öffnen gesendet. Benötigt Notification-Berechtigungen. |
restart Package | Android + iOS | Ruft main() erneut auf | Ruft Darts main() erneut auf, ohne exit(0) oder Notifications. Erwähnt in #127409. |
| Phoenix Pattern | Android + iOS | Widget Tree Rebuild | Nur In-Process — Dart Isolate bleibt am Leben |
Wie das Phoenix Pattern funktioniert
Die Implementierung nutzt Flutters Widget Reconciliation Algorithmus. Wenn Flutter auf ein Widget mit einem anderen Key als zuvor trifft, behandelt es dieses als komplett neues Widget — es disposed das alte und erstellt eine frische Instanz.
Hier die vollständige, lauffähige Implementierung:
import 'package:flutter/material.dart';
/// Wraps the app widget tree and provides a static [rebirth] method
/// to tear down and rebuild the entire tree from scratch.
class Phoenix extends StatefulWidget {
final Widget child;
const Phoenix({super.key, required this.child});
/// Call from anywhere with a valid [BuildContext] to restart the app.
static void rebirth(BuildContext context) {
context.findAncestorStateOfType<_PhoenixState>()!.restartApp();
}
State<Phoenix> createState() => _PhoenixState();
}
class _PhoenixState extends State<Phoenix> {
Key _key = UniqueKey();
void restartApp() {
setState(() {
_key = UniqueKey();
});
}
Widget build(BuildContext context) {
return KeyedSubtree(
key: _key,
child: widget.child,
);
}
} Der Mechanismus Schritt für Schritt:
Phoenixsitzt an der Wurzel des Widget Tree, über allem anderen- Sein Child (deine gesamte App) wird in ein
KeyedSubtreemit einemUniqueKeygewrappt - Wenn
Phoenix.rebirth(context)aufgerufen wird, weistsetStateeinen neuenUniqueKeyzu - Flutters Reconciliation sieht den neuen Key und schließt daraus, dass der alte Subtree nicht mehr existiert
- Jedes Widget darunter wird
dispose()d und von Grund auf neu erstellt — inklusive allerStatefulWidgetStates,InheritedWidget-Daten und Navigation Stacks
KeyedSubtree ist ein Flutter-Framework-Widget, das einem Child einen Key zuweist, ohne Layout- oder Rendering-Overhead hinzuzufügen.
Anwendungsbeispiele
Grundlegendes Setup
Auf runApp-Ebene wrappen:
void main() {
runApp(
Phoenix(
child: MyApp(),
),
);
} Mit Riverpod
Platziere ProviderScope innerhalb von Phoenix, damit alle Provider bei einem Rebirth abgerissen und neu erstellt werden:
void main() {
runApp(
Phoenix(
child: ProviderScope(
child: MyApp(),
),
),
);
} Das stellt sicher, dass jeder Riverpod Provider — inklusive keepAlive-Provider — disposed und mit frischem State neu aufgebaut wird.
Typische Einsatzpunkte
// After user logout
Future<void> handleLogout(BuildContext context) async {
await authService.logout();
await secureStorage.deleteAll();
if (context.mounted) {
Phoenix.rebirth(context);
}
}
// After failed app initialization
if (initFailed) {
Phoenix.rebirth(context);
}
// After a configuration change that affects the whole app
Future<void> onConfigChanged(BuildContext context) async {
await persistNewConfig();
if (context.mounted) {
Phoenix.rebirth(context);
}
} Beachte den context.mounted-Check — nach einem await kann das Widget bereits unmounted sein.
Was zurückgesetzt wird vs. was überlebt
Das wird am häufigsten missverstanden. Das Phoenix Pattern ist ein In-Process Widget Tree Rebuild, kein Neustart auf OS-Ebene. Das Dart VM Isolate bleibt am Leben.
Wird zurückgesetzt (zerstört und neu erstellt):
- Sämtlicher
StatefulWidgetState — jedes Widget ruftdispose()auf, danninitState()auf der neuen Instanz - Alle
InheritedWidget-Daten - Aller Riverpod/Provider State (wenn
ProviderScopeinnerhalb von Phoenix liegt) - Der Navigation Stack — der Router wird ab seiner initialen Route neu erstellt
- In-Memory Caches, die von Widgets oder Providern gehalten werden
Überlebt (unverändert nach Rebirth):
- Statische Variablen und Singletons — sie leben auf dem Dart Heap, außerhalb des Widget Tree
dotenv.env-Werte — werden beim Start einmalig in eine statische Map geladen und nicht neu geladen- HTTP-Client-Instanzen (Dio, http), die außerhalb des Widget Tree erstellt wurden — ihre
baseUrlund Header bleiben bestehen - Firebase-Initialisierung —
Firebase.initializeApp()wirft eineFirebaseException, wenn es ein zweites Mal aufgerufen wird - Nativer Plugin State — Platform Channel Connections bleiben über Widget Rebuilds hinweg bestehen
SharedPreferences-Daten — auf der Festplatte gespeichert, komplett unbetroffen- Secure Storage (
flutter_secure_storage) — persistiert in Keychain/Keystore, unbetroffen
Wann das Phoenix Pattern NICHT verwenden
Backend-Umgebungen zur Laufzeit wechseln
Wenn deine API-Base-URLs aus .env-Dateien kommen, die beim Start geladen werden (z.B. via flutter_dotenv), lädt Phoenix sie nicht neu. HTTP-Clients wie Dio cachen die baseUrl aus der Initialisierung. Der richtige Ansatz sind Flutter Flavors — separate Builds pro Umgebung, jeweils mit eigener .env, Firebase-Konfiguration und Bundle-ID.
Das wurde in Issue #97939 diskutiert, wo ein Entwickler Umgebungen wechseln und alle injizierten Provider vom Root zurücksetzen wollte. Flutter-Contributor @chinmaygarde bestätigte, dass iOS das Neustarten des Prozesses ohne Nutzerinteraktion nicht unterstützt, und schlug das Neuerstellen der FlutterEngine-Instanz als Alternative vor — merkte aber an, dass Plugin State sorgfältig behandelt werden müsste. Ein anderes Flutter-Team-Mitglied, @dnfield, argumentierte dagegen und vertrat die Position, dass Widgets auf Änderungen via MediaQuery oder Localizations lauschen sollten, statt die gesamte App neu zu bauen.
Firebase-Projekte wechseln
Firebase kann im selben Prozess nicht neu initialisiert werden. Wenn Dev und Prod verschiedene Firebase-Projekte nutzen (verschiedene google-services.json), kann Phoenix nicht zwischen ihnen wechseln. Nutze stattdessen Build Flavors mit FlutterFire CLI.
Persistierte Daten löschen
SharedPreferences, Secure Storage und SQLite-Datenbanken überleben einen Phoenix-Restart. Lösche sie vor dem Aufruf von rebirth:
await SharedPreferences.getInstance().then((prefs) => prefs.clear());
await secureStorage.deleteAll();
Phoenix.rebirth(context); flutter_phoenix Package vs. Eigenimplementierung
Das flutter_phoenix Package von The Ring liefert genau dieses Pattern als pub-Abhängigkeit. Die API ist identisch: mit Phoenix wrappen, Phoenix.rebirth(context) aufrufen.
| Ansatz | Vorteile | Nachteile |
|---|---|---|
| Eigenimplementierung (~30 Zeilen) | Keine Abhängigkeit, volle Kontrolle, leicht erweiterbar | Du wartest den Code selbst |
flutter_phoenix Package | Von der Community gewartet, getestet | Zusätzliche Abhängigkeit für minimalen Code |
Die Implementierung ist klein genug, dass ein Kopieren die externe Abhängigkeit vermeidet. Beide Ansätze funktionieren.
Häufig gestellte Fragen
Funktioniert das Phoenix Pattern mit GoRouter, AutoRoute oder anderen Navigations-Packages?
Ja. Da Phoenix den gesamten Widget Tree neu aufbaut, wird der Router von Grund auf mit seiner initialen Konfiguration neu erstellt. Der gesamte Navigation State (aktuelle Route, Stack-Verlauf) geht verloren — was bei einem Restart normalerweise das gewünschte Verhalten ist.
Kann ich Phoenix mit BLoC statt Riverpod verwenden?
Ja. Wenn deine BlocProvider-Widgets innerhalb des Phoenix-gewrappten Trees liegen, werden alle BLoCs geschlossen und neu erstellt. Das gleiche Prinzip gilt — alles im Widget Tree wird neu aufgebaut.
Verursacht Phoenix.rebirth() ein visuelles Flackern oder einen Ladebildschirm?
Der Tree wird synchron im selben Frame neu aufgebaut. Es gibt kein inhärentes Flackern, aber wenn die initState- oder build-Methoden deiner App asynchrones Laden auslösen (mit Spinner), sieht der Nutzer diesen Ladezustand erneut — was bei einem Restart in der Regel angemessen ist.
Ist das Phoenix Pattern sicher für Produktions-Apps?
Ja. Es nutzt Standard-Flutter-APIs (StatefulWidget, Key, KeyedSubtree) ohne Hacks oder plattformspezifischen Code. Das flutter_phoenix Package wird seit 2020 in Produktions-Apps eingesetzt. Ein Hinweis: In Issue #48955 berichteten Entwickler von gelegentlichen Freezes bei UniqueKey-basierten Restarts in Integration Tests — das scheint aber spezifisch für die Test-Harness-Umgebung zu sein, nicht für den Produktionsbetrieb. Flutter-Team-Mitglied @knopp merkte außerdem an, dass Keyboard- und TextInputClient-State nach Restarts in einen unerwarteten Zustand geraten kann — darauf achten, falls bei einem Rebirth Textfelder fokussiert sind.
Zusammenfassung
Das Phoenix Pattern ist eine saubere, abhängigkeitsfreie Lösung für In-Process Flutter-App-Restarts. Es deckt Logout-Flows, Wiederholungsversuche nach fehlgeschlagener Initialisierung und Konfigurationsänderungen ab, die den Widget Tree betreffen. Es funktioniert auf Android und iOS mit ca. 30 Zeilen Code.
Aber es ist kein echter Neustart auf OS-Ebene. Alles, was außerhalb des Widget Tree lebt — Statics, Singletons, Umgebungsvariablen, nativer Plugin State — überlebt den Rebirth. Falls auch diese zurückgesetzt werden müssen, brauchst du einen echten Prozess-Neustart, und Flutter bietet bisher keinen an.
Kenne die Grenze. Nutze es für das, wofür es gut ist.
Weiterführende Links
- Flutter-Doku:
KeyedSubtree-Klasse — das Widget, das Phoenix antreibt - Flutter-Doku: Wann Keys verwenden — Widget Reconciliation verstehen
flutter_phoenixauf pub.dev — die Package-Implementierungrestartauf pub.dev — Alternative, diemain()erneut aufruft, ohne den Prozess zu beenden- Flutter Issue #127409:
restartApp()-Methode hinzufügen — offizieller Feature Request mit Diskussion zu “Soft vs. Hard Restart” - Flutter Issue #97939: Restart auf OS-Ebene — Flutter-Team-Diskussion über iOS-Limitierungen und Engine-Neuerstellung
- Flutter Flavors Setup — der richtige Ansatz für Umgebungswechsel