6 Wege zur Verwaltung des Zustands in Flutter-Apps (mit Codebeispielen)

Wie man den Zustand in Flutter verwaltet

Inhaltsverzeichnis

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:

  1. 🧱 setState() — Der eingebaute, einfachste Ansatz
  2. 🏗️ InheritedWidget — Flutter’s Grundlage für die Zustandsausbreitung
  3. 🪄 Provider — Die empfohlene Lösung für die meisten Apps
  4. 🔒 Riverpod — Moderne, kompiliersichere Weiterentwicklung von Provider
  5. 📦 Bloc — Für skalierbare, unternehmensgerechte Anwendungen
  6. GetX — Leichtgewichtige All-in-One-Lösung

flutter troubles mega tracktor


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.