Appearance
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ügenbash
flutter pub getGrundlegende 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.5Verwendung:
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
- ✅
Providerfü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:
- Erstellen Sie einen Zähler mit
setState()(lokaler Zustand) - Erstellen Sie eine To-Do-Liste mit Provider (cross-Widget Zustand)
- Implementieren Sie eine Theme-Umschaltung (hell/dunkel) mit Provider
- 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
