---
type: Article
title: 'Das Phoenix Pattern in Flutter: App neu starten, ohne sie neu zu starten'
description: '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.'
url: https://khal.it/blog/the-phoenix-pattern-in-flutter
locale: de
timestamp: '2026-03-09'
tags: ['flutter', 'architektur']
---

# 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 `Key`s voraus. Falls du mit Keys noch nicht vertraut bist, starte mit der [Flutter-Dokumentation zu Keys](https://api.flutter.dev/flutter/foundation/Key-class.html)._

**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)](https://github.com/flutter/flutter/issues/127409), der aber nicht umgesetzt wurde. In der Diskussion [schlug](https://github.com/flutter/flutter/issues/127409) 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](https://developer.apple.com/design/human-interface-guidelines/) -- sieht aus wie ein Absturz, Risiko der App-Store-Ablehnung |
| `SystemNavigator.pop()`                                       | Nur Android       | App schließen            | [Macht auf iOS nichts](https://github.com/flutter/flutter/issues/55760)                                                                           |
| [`restart_app`](https://pub.dev/packages/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`](https://pub.dev/packages/restart) Package         | Android + iOS     | Ruft `main()` erneut auf | Ruft Darts `main()` erneut auf, ohne `exit(0)` oder Notifications. [Erwähnt in #127409](https://github.com/flutter/flutter/issues/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](https://docs.flutter.dev/resources/architectural-overview#rendering-and-layout). 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:

```dart
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();
  }

  @override
  State<Phoenix> createState() => _PhoenixState();
}

class _PhoenixState extends State<Phoenix> {
  Key _key = UniqueKey();

  void restartApp() {
    setState(() {
      _key = UniqueKey();
    });
  }

  @override
  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`](https://api.flutter.dev/flutter/widgets/KeyedSubtree-class.html) 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:

```dart
void main() {
  runApp(
    Phoenix(
      child: MyApp(),
    ),
  );
}
```

### Mit Riverpod

Platziere `ProviderScope` **innerhalb** von Phoenix, damit alle Provider bei einem Rebirth abgerissen und neu erstellt werden:

```dart
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

```dart
// 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-Initialisierung** -- [`Firebase.initializeApp()`](https://firebase.google.com/docs/flutter/setup) 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`](https://pub.dev/packages/flutter_dotenv)), lädt Phoenix sie nicht neu. HTTP-Clients wie Dio cachen die `baseUrl` aus der Initialisierung. Der richtige Ansatz sind [Flutter Flavors](https://docs.flutter.dev/deployment/flavors) -- separate Builds pro Umgebung, jeweils mit eigener `.env`, Firebase-Konfiguration und Bundle-ID.

Das wurde [in Issue #97939 diskutiert](https://github.com/flutter/flutter/issues/97939), 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](https://github.com/flutter/flutter/issues/97939) 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](https://codewithandrea.com/articles/flutter-firebase-multiple-flavors-flutterfire-cli/).

### Persistierte Daten löschen

SharedPreferences, Secure Storage und SQLite-Datenbanken überleben einen Phoenix-Restart. Lösche sie **vor** dem Aufruf von `rebirth`:

```dart
await SharedPreferences.getInstance().then((prefs) => prefs.clear());
await secureStorage.deleteAll();
Phoenix.rebirth(context);
```

---

## flutter_phoenix Package vs. Eigenimplementierung

Das [`flutter_phoenix`](https://pub.dev/packages/flutter_phoenix) Package von [The Ring](https://github.com/The-ring-io/flutter_phoenix) 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`](https://pub.dev/packages/flutter_phoenix) Package wird seit 2020 in Produktions-Apps eingesetzt. Ein Hinweis: In [Issue #48955](https://github.com/flutter/flutter/issues/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](https://github.com/flutter/flutter/issues/127409), 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](https://github.com/flutter/flutter/issues/127409).

Kenne die Grenze. Nutze es für das, wofür es gut ist.

---

## Weiterführende Links

- [Flutter-Doku: `KeyedSubtree`-Klasse](https://api.flutter.dev/flutter/widgets/KeyedSubtree-class.html) -- das Widget, das Phoenix antreibt
- [Flutter-Doku: Wann Keys verwenden](https://api.flutter.dev/flutter/foundation/Key-class.html) -- Widget Reconciliation verstehen
- [`flutter_phoenix` auf pub.dev](https://pub.dev/packages/flutter_phoenix) -- die Package-Implementierung
- [`restart` auf pub.dev](https://pub.dev/packages/restart) -- Alternative, die `main()` erneut aufruft, ohne den Prozess zu beenden
- [Flutter Issue #127409: `restartApp()`-Methode hinzufügen](https://github.com/flutter/flutter/issues/127409) -- offizieller Feature Request mit Diskussion zu "Soft vs. Hard Restart"
- [Flutter Issue #97939: Restart auf OS-Ebene](https://github.com/flutter/flutter/issues/97939) -- Flutter-Team-Diskussion über iOS-Limitierungen und Engine-Neuerstellung
- [Flutter Flavors Setup](https://docs.flutter.dev/deployment/flavors) -- der richtige Ansatz für Umgebungswechsel
