Zum Inhalt springen
flutter 2026-03-09

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

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

MethodePlattform-SupportRestart-LevelNachteil
exit(0)Android + iOSOS-Prozess beendenApple rät davon ab — sieht aus wie ein Absturz, Risiko der App-Store-Ablehnung
SystemNavigator.pop()Nur AndroidApp schließenMacht auf iOS nichts
restart_app PackageAndroid + iOSNativer RestartUnter iOS wird die App beendet und eine lokale Notification zum erneuten Öffnen gesendet. Benötigt Notification-Berechtigungen.
restart PackageAndroid + iOSRuft main() erneut aufRuft Darts main() erneut auf, ohne exit(0) oder Notifications. Erwähnt in #127409.
Phoenix PatternAndroid + iOSWidget Tree RebuildNur 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:

  1. Phoenix sitzt an der Wurzel des Widget Tree, über allem anderen
  2. Sein Child (deine gesamte App) wird in ein KeyedSubtree mit einem UniqueKey gewrappt
  3. Wenn Phoenix.rebirth(context) aufgerufen wird, weist setState einen neuen UniqueKey zu
  4. Flutters Reconciliation sieht den neuen Key und schließt daraus, dass der alte Subtree nicht mehr existiert
  5. Jedes Widget darunter wird dispose()d und von Grund auf neu erstellt — inklusive aller StatefulWidget States, 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 StatefulWidget State — jedes Widget ruft dispose() auf, dann initState() auf der neuen Instanz
  • Alle InheritedWidget-Daten
  • Aller Riverpod/Provider State (wenn ProviderScope innerhalb 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 baseUrl und Header bleiben bestehen
  • Firebase-InitialisierungFirebase.initializeApp() wirft eine FirebaseException, 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.

AnsatzVorteileNachteile
Eigenimplementierung (~30 Zeilen)Keine Abhängigkeit, volle Kontrolle, leicht erweiterbarDu wartest den Code selbst
flutter_phoenix PackageVon der Community gewartet, getestetZusä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

KH
Khalit Hartmann Freelance Mobile & Full-Stack Developer