Skip to content

Kapitel 7: State Management

7.1 Konzept der Zustandsverwaltung

Was ist "State" (Zustand)?

Zustand = Daten, die sich während der Laufzeit ändern und die UI beeinflussen.

Beispiele für Zustand:

  • ✅ Zählerwert (0, 1, 2, ...)
  • ✅ Eingabetext (in TextField)
  • ✅ Login-Status (eingeloggt / ausgeloggt)
  • ✅ Theme (hell / dunkel)
  • ✅ Warenkorb (Liste der Produkte)

Warum Zustandsverwaltung?

Problem ohne Zustandsverwaltung:

dart
// Zähler ändert sich, aber UI aktualisiert sich nicht!
int zaehler = 0;

ElevatedButton(
  onPressed: () {
    zaehler++;  // Zähler erhöht sich, aber UI bleibt gleich!
  },
  child: Text('Erhöhen'),
)

Text('$zaehler')  // Zeigt immer "0" an!

Lösung: Zustandsverwaltung verwenden (setState(), Provider, etc.)

7.2 Basis-Zustandsverwaltung (StatefulWidget)

setState() (Einfachste Methode)

setState() ist die einfachste Form der Zustandsverwaltung in Flutter.

dart
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _zaehler = 0;  // Zustandsvariable
  
  void _erhoehen() {
    setState(() {  // WICHTIG: setState() aktualisiert UI
      _zaehler++;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('$_zaehler', style: const TextStyle(fontSize: 48)),
            ElevatedButton(
              onPressed: _erhoehen,
              child: const Text('Erhöhen'),
            ),
          ],
        ),
      ),
    );
  }
}

Wann setState() verwenden?

  • ✅ Einfache Zustände (lokaler Zustand)
  • ✅ Wenige Widgets betroffen
  • ✅ Keine komplexe Zustandsverwaltung notwendig

Einschränkungen:

  • ❌ Zustand kann nicht einfach an andere Widgets weitergegeben werden
  • ❌ Bei komplexen Apps unübersichtlich

Zustand von Eltern zu Kind weitergeben

Methode: Konstruktor-Parameter

dart
// Kind-Widget
class ChildWidget extends StatelessWidget {
  final int zaehler;  // Zustand vom Eltern-Widget
  
  const ChildWidget(this.zaehler, {super.key});
  
  @override
  Widget build(BuildContext context) {
    return Text('Zähler vom Eltern: $zaehler');
  }
}

// Eltern-Widget
class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int _zaehler = 0;
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        // Zustand an Kind weitergeben
        ChildWidget(_zaehler),
        ElevatedButton(
          onPressed: () {
            setState(() {
              _zaehler++;
            });
          },
          child: const Text('Erhöhen'),
        ),
      ],
    );
  }
}

7.3 Eltern-Kind-Zustandskommunikation

Kind zu Eltern (Callback-Methode)

Methode: Callback-Funktion als Parameter übergeben

dart
// Kind-Widget (sendet Daten an Eltern)
class ChildWidget extends StatelessWidget {
  final Function(int) onCounterChanged;  // Callback-Funktion
  
  const ChildWidget(this.onCounterChanged, {super.key});
  
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        onCounterChanged(1);  // Benachrichtigt Eltern
      },
      child: const Text('Erhöhen (Kind)'),
    );
  }
}

// Eltern-Widget (empfängt Daten von Kind)
class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State<ParentWidget> createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int _zaehler = 0;
  
  // Callback-Funktion (wird von Kind aufgerufen)
  void _handleCounterChanged(int value) {
    setState(() {
      _zaehler += value;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text('Zähler: $_zaehler'),
        // Callback an Kind übergeben
        ChildWidget(_handleCounterChanged),
      ],
    );
  }
}

Bidirektionale Kommunikation

dart
// Kind-Widget
class ChildWidget extends StatelessWidget {
  final int zaehler;  // Vom Eltern (Eltern → Kind)
  final Function(int) onCounterChanged;  // Zum Eltern (Kind → Eltern)
  
  const ChildWidget(this.zaehler, this.onCounterChanged, {super.key});
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text('Zähler: $zaehler'),  // Eltern → Kind
        ElevatedButton(
          onPressed: () {
            onCounterChanged(1);  // Kind → Eltern
          },
          child: const Text('Erhöhen'),
        ),
      ],
    );
  }
}

7.4 Cross-Widget Zustandsverwaltung (Provider - empfohlen)

Warum Provider?

Problem: Wenn viele Widgets den gleichen Zustand benötigen, wird setState() unübersichtlich.

Lösung: Provider (offiziell empfohlen von Flutter-Team)

Provider installieren

pubspec.yaml:

yaml
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.0  # Provider-Paket hinzufügen
bash
flutter pub get

Grundlegende Verwendung (3 Schritte)

Schritt 1: Zustands-Klasse definieren (ChangeNotifier)

dart
import 'package:flutter/material.dart';

// Zustands-Klasse (verwaltet Zustand)
class CounterProvider extends ChangeNotifier {
  int _zaehler = 0;
  
  int get zaehler => _zaehler;  // Getter (zum Lesen)
  
  void erhoehen() {
    _zaehler++;
    notifyListeners();  // WICHTIG: Benachrichtigt alle Zuhörer!
  }
  
  void zuruecksetzen() {
    _zaehler = 0;
    notifyListeners();
  }
}

Schritt 2: Zustand bereitstellen (Provider)

dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CounterProvider(),  // Zustand bereitstellen
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Provider Beispiel')),
        body: const CounterPage(),
      ),
    );
  }
}

Schritt 3: Zustand verbrauchen (Consumer / Provider.of)

Methode 1: Consumer (empfohlen)

dart
class CounterPage extends StatelessWidget {
  const CounterPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          // Consumer: Reagiert auf Zustandsänderungen
          Consumer<CounterProvider>(
            builder: (context, provider, child) {
              return Text(
                '${provider.zaehler}',  // Zustand lesen
                style: const TextStyle(fontSize: 48),
              );
            },
          ),
          const SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              // Zustand ändern
              Provider.of<CounterProvider>(context, listen: false).erhoehen();
            },
            child: const Text('Erhöhen'),
          ),
        ],
      ),
    );
  }
}

Methode 2: Provider.of (direkter Zugriff)

dart
// Zustand lesen (mit listen: true → reagiert auf Änderungen)
final provider = Provider.of<CounterProvider>(context);

// Zustand ändern (mit listen: false → keine Reaktion)
Provider.of<CounterProvider>(context, listen: false).erhoehen();

Zusammenfassung: Provider-Ablauf

1. Zustands-Klasse definieren (ChangeNotifier)

2. Zustand bereitstellen (ChangeNotifierProvider)

3. Zustand verbrauchen (Consumer / Provider.of)

7.5 Andere Zustandsverwaltungs-Lösungen (Überblick)

GetX (Einfach, beliebt)

Vorteile:

  • ✅ Sehr einfach zu verwenden
  • ✅ Kombiniert Zustandsverwaltung, Routing, Abhängigkeitsinjektion
  • ✅ Weniger Boilerplate-Code

Installation:

yaml
dependencies:
  get: ^4.6.5

Verwendung:

dart
import 'package:get/get.dart';

// Zustands-Klasse
class CounterController extends GetxController {
  int zaehler = 0;
  
  void erhoehen() {
    zaehler++;
    update();  // Benachrichtigt UI
  }
}

// Verwenden
class CounterPage extends StatelessWidget {
  final controller = Get.put(CounterController());
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            GetBuilder<CounterController>(
              builder: (c) => Text('${c.zaehler}'),
            ),
            ElevatedButton(
              onPressed: controller.erhoehen,
              child: const Text('Erhöhen'),
            ),
          ],
        ),
      ),
    );
  }
}

Bloc (Für große Projekte)

Vorteile:

  • ✅ Sehr strukturiert (Trennung von Business-Logik und UI)
  • ✅ Gute Testbarkeit
  • ✅ Beliebt in Enterprise-Anwendungen

Nachteile:

  • ❌ Komplexer (viel Boilerplate-Code)
  • ❌ Schwieriger für Anfänger

Für Anfänger: Bleiben Sie bei setState() oder Provider.

7.6 Praxisbeispiel: Theme-Umschaltung mit Provider

Schritt 1: Theme-Provider definieren

dart
import 'package:flutter/material.dart';

class ThemeProvider extends ChangeNotifier {
  bool _isDarkMode = false;
  
  bool get isDarkMode => _isDarkMode;
  
  ThemeData get themeData => _isDarkMode ? ThemeData.dark() : ThemeData.light();
  
  void toggleTheme() {
    _isDarkMode = !_isDarkMode;
    notifyListeners();
  }
}

Schritt 2: In main.dart bereitstellen

dart
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => ThemeProvider(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final themeProvider = Provider.of<ThemeProvider>(context);
    
    return MaterialApp(
      theme: themeProvider.themeData,  // Dynamisches Theme
      home: const HomePage(),
    );
  }
}

Schritt 3: In UI verwenden (Theme umschalten)

dart
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final themeProvider = Provider.of<ThemeProvider>(context, listen: false);
    
    return Scaffold(
      appBar: AppBar(title: const Text('Theme-Umschaltung')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            themeProvider.toggleTheme();  // Theme umschalten
          },
          child: const Text('Theme umschalten'),
        ),
      ),
    );
  }
}

7.7 Häufige Fehler

Fehler 1: setState() vergessen

Fehlerhaft:

dart
void _erhoehen() {
  _zaehler++;  // UI wird NICHT aktualisiert!
}

Richtig:

dart
void _erhoehen() {
  setState(() {
    _zaehler++;  // UI wird aktualisiert!
  });
}

Fehler 2: Cross-Widget Zustand mit setState() versuchen

Fehler: Zustand kann nicht einfach zwischen nicht-verwandten Widgets geteilt werden.

Lösung: Provider oder andere Zustandsverwaltungs-Lösung verwenden.

Fehler 3: notifyListeners() vergessen (bei Provider)

Fehlerhaft:

dart
void erhoehen() {
  _zaehler++;
  // notifyListeners() vergessen! UI wird nicht aktualisiert.
}

Richtig:

dart
void erhoehen() {
  _zaehler++;
  notifyListeners();  // UI wird aktualisiert!
}

Zusammenfassung

In diesem Kapitel haben Sie:

  • ✅ Verstanden, was "State" (Zustand) ist und warum Zustandsverwaltung notwendig ist
  • setState() für lokale Zustandsverwaltung verwendet
  • ✅ Zustand von Eltern zu Kind (Konstruktor-Parameter) weitergegeben
  • ✅ Zustand von Kind zu Eltern (Callback) weitergegeben
  • Provider für cross-Widget Zustandsverwaltung gemeistert
  • ✅ Andere Lösungen (GetX, Bloc) überblickt
  • ✅ Ein Praxisbeispiel (Theme-Umschaltung) implementiert
  • ✅ Häufige Fehler behoben

Nächstes Kapitel: Wir werden Routing & Navigation lernen (Seitenwechsel).


Übungsaufgaben:

  1. Erstellen Sie einen Zähler mit setState() (lokaler Zustand)
  2. Erstellen Sie eine To-Do-Liste mit Provider (cross-Widget Zustand)
  3. Implementieren Sie eine Theme-Umschaltung (hell/dunkel) mit Provider
  4. Erstellen Sie ein Warenkorb-System mit Provider (Hinzufügen/Entfernen von Produkten)

Häufige Fehler:

  • setState() vergessen → UI aktualisiert sich nicht
  • notifyListeners() bei Provider vergessen → UI wird nicht aktualisiert
  • ❌ Zustand komplexer machen als notwendig → Bei einfachen Zuständen setState() verwenden!
  • ❌ Provider nicht korrekt bereitstellen → Provider.of() wirft Fehler

Frei für alle Anfänger