6 Wege zur Verwaltung des Zustands in Flutter-Apps (mit Codebeispielen)
Wie man den Zustand in Flutter verwaltet
In diesem Artikel werden wir sechs beliebte Methoden zur Verwaltung des Zustands in Flutter-Apps erkunden, einschließlich echter Beispiele und Best Practices:
Zustandsverwaltung ist eines der wichtigsten - und am meisten diskutierten - Themen in der Flutter-Entwicklung. Sie bestimmt, wie Ihre App Änderungen in Daten verarbeitet und die UI effizient aktualisiert. Flutter bietet Ihnen mehrere Strategien zur Zustandsverwaltung — von einfach bis hoch skalierbar.
Die Strategien zur Zustandsverwaltung in Flutter sind:
- 🧱
setState()
— Der eingebaute, einfachste Ansatz - 🏗️ InheritedWidget — Flutter’s Grundlage für die Zustandsausbreitung
- 🪄 Provider — Die empfohlene Lösung für die meisten Apps
- 🔒 Riverpod — Moderne, kompiliersichere Weiterentwicklung von Provider
- 📦 Bloc — Für skalierbare, unternehmensgerechte Anwendungen
- ⚡ GetX — Leichtgewichtige All-in-One-Lösung
1. Verwendung von setState()
— Die Grundlagen
Die einfachste Methode zur Verwaltung des Zustands in Flutter besteht darin, die eingebaute setState()
-Methode in einem StatefulWidget
zu verwenden.
Dieser Ansatz ist ideal für lokale UI-Zustände — wo der Zustand zu einem Widget gehört und nicht im gesamten App geteilt werden muss.
Beispiel: Zähler-App mit setState()
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: CounterScreen());
}
}
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('setState Zähler')),
body: Center(
child: Text('Zähler: $_count', style: TextStyle(fontSize: 24)),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: Icon(Icons.add),
),
);
}
}
Vorteile
- Sehr einfach zu implementieren
- Ideal für lokale oder temporäre UI-Zustände
- Keine externen Abhängigkeiten
Nachteile
- Skaliert nicht für große Apps
- Schwierig, Zustand zwischen Widgets zu teilen
- Logik ist mit der UI vermischt
Verwenden Sie es, wenn:
- Sie Prototypen erstellen oder kleine Widgets bauen
- Sie isolierte UI-Zustände verwalten (z. B. ein Button-Toggle, Anzeige eines Modals)
2. InheritedWidget — Flutter’s Grundlage
InheritedWidget
ist der Low-Level-Mechanismus, den Flutter verwendet, um Daten durch den Widget-Baum zu verbreiten. Die meisten Zustandsverwaltungslösungen (einschließlich Provider) sind darauf aufgebaut.
Das Verständnis von InheritedWidget hilft Ihnen, zu verstehen, wie Flutter’s Zustandsverwaltung unter der Haube funktioniert.
Beispiel: Theme-Manager mit InheritedWidget
import 'package:flutter/material.dart';
// Das InheritedWidget, das die Theme-Daten hält
class AppTheme extends InheritedWidget {
final bool isDarkMode;
final Function toggleTheme;
const AppTheme({
Key? key,
required this.isDarkMode,
required this.toggleTheme,
required Widget child,
}) : super(key: key, child: child);
// Zugriffsmethode, um das nächstgelegene AppTheme im Widget-Baum zu erhalten
static AppTheme? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<AppTheme>();
}
@override
bool updateShouldNotify(AppTheme oldWidget) {
return isDarkMode != oldWidget.isDarkMode;
}
}
// Stateful-Wrapper zur Verwaltung von Zustandsänderungen
class ThemeManager extends StatefulWidget {
final Widget child;
const ThemeManager({Key? key, required this.child}) : super(key: key);
@override
_ThemeManagerState createState() => _ThemeManagerState();
}
class _ThemeManagerState extends State<ThemeManager> {
bool _isDarkMode = false;
void _toggleTheme() {
setState(() {
_isDarkMode = !_isDarkMode;
});
}
@override
Widget build(BuildContext context) {
return AppTheme(
isDarkMode: _isDarkMode,
toggleTheme: _toggleTheme,
child: widget.child,
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ThemeManager(
child: Builder(
builder: (context) {
final theme = AppTheme.of(context);
return MaterialApp(
theme: theme!.isDarkMode ? ThemeData.dark() : ThemeData.light(),
home: HomeScreen(),
);
},
),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = AppTheme.of(context);
return Scaffold(
appBar: AppBar(title: Text('InheritedWidget Theme')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Aktuelles Theme: ${theme!.isDarkMode ? "Dunkel" : "Hell"}',
style: TextStyle(fontSize: 20),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => theme.toggleTheme(),
child: Text('Theme umschalten'),
),
],
),
),
);
}
}
Vorteile
- In Flutter integriert — keine externen Abhängigkeiten
- Effiziente Widget-Neuerstellungen
- Grundlage für das Verständnis anderer Lösungen
- Direkte Kontrolle über die Ausbreitungslogik
Nachteile
- Umständlicher Boilerplate-Code
- Erfordert einen Wrapper StatefulWidget
- Leicht Fehler zu machen
- Nicht anfängerfreundlich
Verwenden Sie es, wenn:
- Sie lernen möchten, wie Flutter’s Zustandsverwaltung intern funktioniert
- Sie benutzerdefinierte Zustandsverwaltungslösungen erstellen
- Sie sehr spezifische Kontrolle über die Zustandsausbreitung benötigen
3. Provider — Die von Flutter empfohlene Lösung
Wenn der Zustand Ihrer App zwischen mehreren Widgets geteilt werden muss, kommt Provider zur Rettung.
Provider basiert auf Inversion of Control — anstatt dass Widgets den Zustand besitzen, stellt ein Provider ihn für andere zur Verfügung. Das Flutter-Team empfiehlt es offiziell für mittlere Apps.
Setup
Fügen Sie dies zu Ihrer pubspec.yaml
hinzu:
dependencies:
provider: ^6.0.5
Beispiel: Zähler-App mit Provider
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: MyApp(),
),
);
}
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: CounterScreen());
}
}
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = context.watch<CounterModel>();
return Scaffold(
appBar: AppBar(title: Text('Provider Zähler')),
body: Center(
child: Text('Zähler: ${counter.count}', style: TextStyle(fontSize: 24)),
),
floatingActionButton: FloatingActionButton(
onPressed: context.read<CounterModel>().increment,
child: Icon(Icons.add),
),
);
}
}
Vorteile
- Reaktive und effiziente Aktualisierungen
- Saubere Trennung von UI und Logik
- Gut dokumentiert und community-unterstützt
Nachteile
- Etwas mehr Boilerplate-Code als
setState()
- Verschachtelte Provider können komplex werden
Verwenden Sie es, wenn:
- Sie geteilten Zustand über mehrere Widgets benötigen
- Sie ein reaktives Muster ohne Komplexität wünschen
- Ihre App über die Prototypengröße hinauswächst
4. Riverpod — Die moderne Weiterentwicklung von Provider
Riverpod ist eine vollständige Neuschreibung von Provider, die die Abhängigkeit von BuildContext entfernt und Compile-Time-Sicherheit hinzufügt. Es wurde entwickelt, um die Einschränkungen von Provider zu lösen, während dieselbe Philosophie beibehalten wird.
Setup
Fügen Sie dies zu Ihrer pubspec.yaml
hinzu:
dependencies:
flutter_riverpod: ^2.4.0
Beispiel: Benutzerprofil mit Riverpod
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Modell
class UserProfile {
final String name;
final int age;
UserProfile({required this.name, required this.age});
UserProfile copyWith({String? name, int? age}) {
return UserProfile(
name: name ?? this.name,
age: age ?? this.age,
);
}
}
// State Notifier
class ProfileNotifier extends StateNotifier<UserProfile> {
ProfileNotifier() : super(UserProfile(name: 'Gast', age: 0));
void updateName(String name) {
state = state.copyWith(name: name);
}
void updateAge(int age) {
state = state.copyWith(age: age);
}
}
// Provider
final profileProvider = StateNotifierProvider<ProfileNotifier, UserProfile>((ref) {
return ProfileNotifier();
});
// Berechneter Provider
final greetingProvider = Provider<String>((ref) {
final profile = ref.watch(profileProvider);
return 'Hallo, ${profile.name}! Sie sind ${profile.age} Jahre alt.';
});
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: ProfileScreen());
}
}
class ProfileScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final profile = ref.watch(profileProvider);
final greeting = ref.watch(greetingProvider);
return Scaffold(
appBar: AppBar(title: Text('Riverpod Profil')),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(greeting, style: TextStyle(fontSize: 20)),
SizedBox(height: 30),
TextField(
decoration: InputDecoration(labelText: 'Name'),
onChanged: (value) {
ref.read(profileProvider.notifier).updateName(value);
},
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Alter: ${profile.age}', style: TextStyle(fontSize: 18)),
SizedBox(width: 20),
ElevatedButton(
onPressed: () {
ref.read(profileProvider.notifier).updateAge(profile.age + 1);
},
child: Text('+'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
if (profile.age > 0) {
ref.read(profileProvider.notifier).updateAge(profile.age - 1);
}
},
child: Text('-'),
),
],
),
],
),
),
);
}
}
Vorteile
- Keine BuildContext-Abhängigkeit — Zustand überall zugänglich
- Compile-Time-Sicherheit — Fehler vor der Laufzeit erkennen
- Bessere Testbarkeit
- Leistungsstarke Zustandskomposition
- Keine Speicherlecks
Nachteile
- Andere API als traditionelle Flutter-Muster
- Steilere Lernkurve als Provider
- Kleinere Community (aber schnell wachsend)
Verwenden Sie es, wenn:
- Sie ein neues Flutter-Projekt starten
- Sie Typsicherheit und Compile-Time-Garantien wünschen
- Komplexe Zustandsabhängigkeiten und -kompositionen
- Arbeit an mittelgroßen bis großen Anwendungen
5. BLoC (Business Logic Component) — Für Enterprise-Anwendungen
Das BLoC-Muster ist eine fortgeschrittene Architektur, die die Geschäftslogik vollständig von der UI trennt, indem Streams verwendet werden.
Es ist ideal für groß angelegte oder Unternehmensanwendungen, bei denen vorhersehbare und testbare Zustandsübergänge entscheidend sind.
Setup
Fügen Sie diese Abhängigkeit zu Ihrem Projekt hinzu:
dependencies:
flutter_bloc: ^9.0.0
Beispiel: Zähler-App mit BLoC
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// Ereignisse
abstract class CounterEvent {}
class Increment extends CounterEvent {}
// Bloc
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterBloc(),
child: MaterialApp(home: CounterScreen()),
);
}
}
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Bloc Zähler')),
body: Center(
child: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Text('Zähler: $count', style: TextStyle(fontSize: 24));
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Icon(Icons.add),
),
);
}
}
Vorteile
- Skalierbar und wartbar für komplexe Apps
- Klare Trennung der Ebenen
- Einfache Einheitstests
Nachteile
- Mehr Boilerplate
- Steilere Lernkurve
Verwenden Sie es, wenn:
- Sie Unternehmens- oder langfristige Projekte entwickeln
- Sie vorhersehbare und testbare Logik benötigen
- Mehrere Entwickler an verschiedenen App-Modulen arbeiten
6. GetX — Die All-in-One-Leichtgewichts-Lösung
GetX ist eine minimalistische, aber leistungsstarke Zustandsverwaltungslösung, die auch Routing, Dependency Injection und Utilities umfasst. Es ist bekannt für den geringsten Boilerplate aller Lösungen.
Setup
Fügen Sie dies zu Ihrer pubspec.yaml
hinzu:
dependencies:
get: ^4.6.5
Beispiel: Einkaufsliste mit GetX
import 'package:flutter/material.dart';
import 'package:get/get.dart';
// Controller
class ShoppingController extends GetxController {
// Beobachtbare Liste
var items = <String>[].obs;
// Beobachtbarer Zähler
var totalItems = 0.obs;
void addItem(String item) {
if (item.isNotEmpty) {
items.add(item);
totalItems.value = items.length;
}
}
void removeItem(int index) {
items.removeAt(index);
totalItems.value = items.length;
}
void clearAll() {
items.clear();
totalItems.value = 0;
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'GetX Einkaufsliste',
home: ShoppingScreen(),
);
}
}
class ShoppingScreen extends StatelessWidget {
// Controller initialisieren
final ShoppingController controller = Get.put(ShoppingController());
final TextEditingController textController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GetX Einkaufsliste'),
actions: [
Obx(() => Padding(
padding: EdgeInsets.all(16),
child: Center(
child: Text(
'Artikel: ${controller.totalItems}',
style: TextStyle(fontSize: 18),
),
),
)),
],
),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
controller: textController,
decoration: InputDecoration(
hintText: 'Artikelname eingeben',
border: OutlineInputBorder(),
),
),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
controller.addItem(textController.text);
textController.clear();
},
child: Icon(Icons.add),
),
],
),
),
Expanded(
child: Obx(() {
if (controller.items.isEmpty) {
return Center(
child: Text('Keine Artikel in Ihrer Liste'),
);
}
return ListView.builder(
itemCount: controller.items.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text(controller.items[index]),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => controller.removeItem(index),
),
);
},
);
}),
),
if (controller.items.isNotEmpty)
Padding(
padding: EdgeInsets.all(16),
child: ElevatedButton(
onPressed: () {
Get.defaultDialog(
title: 'Liste leeren',
middleText: 'Sind Sie sicher, dass Sie alle Artikel löschen möchten?',
textConfirm: 'Ja',
textCancel: 'Nein',
onConfirm: () {
controller.clearAll();
Get.back();
},
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
minimumSize: Size(double.infinity, 50),
),
child: Text('Alles löschen'),
),
),
],
),
);
}
}
Vorteile
- Minimaler Boilerplate — schnellste Entwicklung
- All-in-One-Lösung (Zustand, Routing, DI, Snackbars, Dialoge)
- Kein BuildContext erforderlich
- Sehr leichtgewichtig und schnell
- Einfach zu erlernen
Nachteile
- Weniger “Flutter-ähnlich” im Vergleich zu Provider/BLoC
- Kann zu starker Kopplung führen, wenn nicht vorsichtig
- Kleinere Ökosystem als Provider
- Einige Entwickler finden es “zu magisch”
Verwenden Sie es, wenn:
- Schnelle Prototypen oder MVP-Entwicklung
- Kleine bis mittlere Apps
- Sie minimalen Boilerplate wünschen
- Das Team Einfachheit gegenüber strenger Architektur bevorzugt
Die richtige State-Management-Lösung auswählen
Lösung | Komplexität | Boilerplate | Lernkurve | Beste Verwendung |
---|---|---|---|---|
🧱 setState() |
Gering | Minimal | Einfach | Einfacher lokaler Zustand, Prototypen |
🏗️ InheritedWidget |
Mittel | Hoch | Mittel | Flutter-Internals lernen, benutzerdefinierte Lösungen |
🪄 Provider |
Gering-Mittel | Gering | Einfach | Die meisten Apps, geteilter Zustand |
🔒 Riverpod |
Mittel | Gering-Mittel | Mittel | Moderne Apps, Typsicherheit |
📦 BLoC |
Hoch | Hoch | Steil | Unternehmensanwendungen, komplexe Geschäftslogik |
⚡ GetX |
Gering | Minimal | Einfach | Schnelle Entwicklung, MVPs |
Schnelle Entscheidungsleitfaden:
App-Komplexität | Empfohlener Ansatz | Beispielverwendung |
---|---|---|
🪶 Einfach | setState() oder GetX |
Schalter, Animationen, kleine Widgets |
⚖️ Mittel | Provider oder Riverpod |
Geteilte Themen, Benutzereinstellungen, Daten-Caching |
🏗️ Komplex | BLoC oder Riverpod |
E-Commerce, Chat-Apps, Finanz-Dashboards |
Abschließende Gedanken
Es gibt keine universelle Lösung für State-Management in Flutter.
Hier ist ein praktischer Ansatz:
- Fangen Sie klein an mit
setState()
für einfache Widgets und Prototypen - Lernen Sie die Grundlagen mit InheritedWidget, um zu verstehen, wie Flutter intern funktioniert
- Wechseln Sie zu Provider, wenn Ihre App wächst und geteilten Zustand benötigt
- Erwägen Sie Riverpod für neue Projekte, bei denen Typsicherheit und moderne Muster wichtig sind
- Setzen Sie BLoC für große Unternehmensanwendungen mit komplexer Geschäftslogik ein
- Probieren Sie GetX aus, wenn schnelle Entwicklung und minimaler Boilerplate Priorität haben
Wichtige Erkenntnisse:
✅ Übertreiben Sie es nicht — verwenden Sie setState()
, bis Sie mehr benötigen
✅ Provider und Riverpod sind hervorragende Wahl für die meisten Anwendungen
✅ BLoC glänzt in großen Teams und komplexen Apps
✅ GetX ist großartig für Geschwindigkeit, aber achten Sie auf Kopplung
✅ Das Verständnis von InheritedWidget hilft Ihnen, jede Lösung zu meistern
Der Schlüssel ist es, Einfachheit, Skalierbarkeit und Wartbarkeit auszubalancieren — und das richtige Werkzeug für Ihre spezifischen Bedürfnisse, Team-Expertise und Projektanforderungen zu wählen.
Flutter State-Management-Bibliotheken Links
- Flutter Offizielle Dokumentation zu State-Management
- Provider-Paket
- Riverpod-Dokumentation
- BLoC-Bibliothek
- GetX-Dokumentation