Plattformübergreifend entwickeln mit Flutter – Teil 4

State Management mit Provider

State Management mit Provider

Plattformübergreifend entwickeln mit Flutter – Teil 4

State Management mit Provider


Die Verwaltung des Anwendungszustands ist ein essenzieller Bestandteil jeder Flutter-App, insbesondere bei komplexen Anwendungen mit dynamischen Inhalten. Die Provider-Bibliothek bietet eine einfache und effiziente Lösung, um den Zustand sauber zu verwalten und über verschiedene Widgets hinweg zugänglich zu machen. In diesem Teil der Artikelserie erfahren Sie, wie Sie Provider in Ihre Flutter-App integrieren, den Zustand verwalten und bewährte Praktiken anwenden, um eine flexible und skalierbare Architektur zu schaffen. Zudem geht natürlich das integrierte Tutorial zur Programmiersprache Dart in die nächste Runde.

Die Zustandsverwaltung ist ein wesentlicher Bestandteil der Entwicklung von Flutter-Anwendungen [1] und trägt dazu bei, eine reibungslose Benutzererfahrung zu gewährleisten. In einer Flutter-App stellt der Zustand sämtliche Daten dar, die sich während der Nutzung verändern können, wie Benutzereingaben, API-Daten oder UI-Interaktionen. Eine gut strukturierte Zustandsverwaltung sorgt dafür, dass Änderungen an einer zentralen Stelle erfasst und an die relevanten Widgets weitergegeben werden. Dadurch bleibt der Code modular, leicht wartbar und wiederverwendbar. Die Hauptziele der Zustandsverwaltung sind die Konsistenz der App, eine effiziente Performance, die saubere Trennung von UI und Logik sowie eine verbesserte Testbarkeit der Anwendung. Insbesondere in größeren Apps wird eine klare Trennung zwischen dem Anwendungszustand und der Darstellungsebene wichtig, um unnötige Neuberechnungen der Benutzeroberfläche zu vermeiden und den Überblick über komplexe Datenflüsse zu behalten. Durch eine effektive Zustandsverwaltung kann der Entwicklungsprozess optimiert werden, indem redundanter Code vermieden und die Reaktionsfähigkeit der Anwendung verbessert wird. Beispielsweise können UI-Elemente automatisch aktualisiert werden, wenn sich relevante Daten ändern.

Über den Status von Objekten hatten wir in Flutter doch schon einmal in dieser Serie geschrieben? Richtig, im zweiten Teil der Artikelserie hatten wir ein verwandtes Thema. Es ging um Widgets, konkret um Stateless und Stateful Widgets. Wir grenzen die beiden Themen daher zunächst voneinander ab.

Abgrenzung zu Stateless und Stateful Widgets

Obwohl Stateless und Stateful Widgets in Flutter eine Form der Zustandsverwaltung darstellen, handelt es sich dabei um eine lokale und User-Interface-spezifische Lösung, die sich hauptsächlich auf einen kleinen Bereich der Benutzeroberfläche konzentriert. Stateful Widgets eignen sich gut für kleinere, unabhängige Zustandsänderungen wie das Aktualisieren eines Zählers oder das Umschalten einer Schaltfläche. Sie sind jedoch nicht für komplexe Anwendungen geeignet, in denen mehrere Widgets auf denselben Zustand zugreifen müssen oder der Zustand global verfügbar sein muss [2], [3].

Im Gegensatz dazu ermöglicht eine zentrale Zustandsverwaltungslösung wie Provider, den Zustand außerhalb der User-Interface-Hierarchie zu verwalten und ihn gezielt in mehreren Teilen der App verfügbar zu machen. Das ist besonders wichtig, wenn Daten von verschiedenen Widgets gemeinsam genutzt werden müssen, beispielsweise bei einer Benutzer-Authentifizierung oder der Verwaltung von API-Daten. Während Stateful Widgets auf die Methode setState() angewiesen sind, um das User Interface zu aktualisieren, verwendet Provider eine reaktive Architektur, bei der sich Widgets automatisch bei Zustandsänderungen aktualisieren. Beide Varianten sind in Tabelle 1 gegenübergestellt.

Merkmal

Stateless/Stateful Widgets

Provider (State Management)

Zustandsbereich

lokal (innerhalb eines einzelnen Widgets)

global oder App-weit verfügbar

Änderungsmechanismus

setState() (nur für Stateful Widgets)

notifyListeners() für reaktiven Zugriff

Datenfluss

Top-Down (über Konstruktoren)

Bottom-up oder über direkten Zugriff

Komplexität

einfach für kleine Änderungen

geeignet für komplexe Anwendungen

Codewartbarkeit

kann schnell unübersichtlich werden

saubere Trennung von UI und Logik

Performance

häufige Neuberechnungen

effiziente, gezielte UI-Updates

Wiederverwendbarkeit

schwierig, da Zustand lokal bleibt

einfach durch zentrale Verwaltung

bestens geeignet für

UI-Elemente mit lokalem Verhalten

komplexe Datenlogik und API-Handling

Beispiel

ein einfacher Button-Click-Zähler

Benutzerverwaltung oder Theme-Änderungen

Tabelle 1: Stateless/Stateful Widgets vs. Provider für das State Management in Flutter-Apps

Während Stateless und Stateful Widgets sich hervorragend für lokale und einfache Zustandsänderungen eignen, ist eine State-Management-Lösung wie Provider ideal für globale und gemeinsam genutzte Zustände, die eine klar strukturierte Architektur erfordern. Die Entscheidung, ob Stateful Widgets oder ein State Management verwendet wird, hängt von der Komplexität der App und den Anforderungen an die Wartbarkeit und Skalierbarkeit ab.

Ansätze zur globalen Zustandsverwaltung in Flutter im Überblick

Die Verwaltung des globalen Zustands in Flutter ist entscheidend für die Architektur und Performance einer App. Es gibt verschiedene Methoden und Ansätze, die je nach Anwendungsfall eingesetzt werden können. In diesem Abschnitt werden die wichtigsten Methoden zur globalen Zustandsverwaltung vorgestellt sowie Kriterien zur Auswahl des passenden Ansatzes erläutert. Es gibt mehrere Ansätze zur globalen Zustandsverwaltung in Flutter, von einfachen Built-in-Mechanismen bis hin zu komplexen State-Management-Lösungen. Die wichtigsten Methoden sind:

  • InheritedWidget/InheritedModel (Flutter-intern)

  • Provider (offiziell empfohlen)

  • Riverpod (moderne Weiterentwicklung von Provider)

  • GetX (leichtgewichtig und einfach)

  • BLoC (Business Logic Component)

  • Redux (inspiriert von React-Redux)

  • MobX (reaktive Zustandsverwaltung)

Die ersten beiden Ansätze erläutern wie ausführlich und auch mit Hilfe von Beispielen. Zu anderen Ansätzen geben wir lediglich einen Überblick.

InheritedWidget/InheritedModel

Flutter bietet mit InheritedWidget eine native Möglichkeit, den Zustand über die Widget-Hierarchie hinweg zu teilen. InheritedModel ist eine Erweiterung, die gezieltere Abfragen auf Teile eines Zustands erlaubt [4]. Dieser Ansatz bietet folgende Vorteile:

  • keine zusätzlichen Abhängigkeiten, da es nativ in Flutter integriert ist

  • effizient, da nur relevante Widgets neu aufgebaut werden

  • gut geeignet für kleine bis mittelgroße Anwendungen

Die Nachteile sind:

  • Manuelle Implementierung kann komplex sein

  • Schwerer wartbar bei wachsender Komplexität

Ein Beispiel zeigt Listing 1, das wir nachfolgend erläutern.

Listing 1

import 'package:flutter/material.dart';
void main() {
  runApp(MyApp());
}
// Definition des InheritedWidgets zur Zustandsverwaltung
class AppState extends InheritedWidget {
  final int counter;
  final Widget child;
  AppState({required this.counter, required this.child}) : super(child: child);
  static AppState? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<AppState>();
  }
  @override
  bool updateShouldNotify(AppState oldWidget) {
    return oldWidget.counter != counter;
  }
}

// Hauptanwendung, die den Zustand bereitstellt
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return AppState(
      counter: _counter,
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(title: Text("InheritedWidget Demo")),
          body: CounterScreen(),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            child: Icon(Icons.add),
          ),
        ),
      ),
    );
  }
}

// Ein Bildschirm, der den globalen Zustand nutzt
class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final appState = AppState.of(context);

    return Center(
      child: Text(
        'Zähler: ${appState?.counter ?? 0}',
        style: TextStyle(fontSize: 24),
      ),
    );
  }
}

AppState – das InheritedWidget

Das InheritedWidget dient als zentrale Stelle zur Zustandsverwaltung und stellt den Zählerwert (counter) global für alle Widgets bereit. Es gibt folgende Eigenschaften:

  • counter speichert den aktuellen Wert des Zählers.

  • child ist das untergeordnete Widget (z. B. die gesamte App).

Die Methode of(BuildContext context) ermöglicht es Widgets, auf den Zustand zuzugreifen. Sie verwendet context.dependOnInheritedWidgetOfExactType<AppState>(), um eine Referenz auf das InheritedWidget zu erhalten.

Die Methode updateShouldNotify wird aufgerufen, wenn sich der Zustand ändert. In unserem Fall wird ein Rebuild nur durchgeführt, wenn der counter-Wert aktualisiert wurde.

MyApp – Stateful Widget mit Zustand

Dieses Stateful Widget hält den Zustand der Anwendung, insbesondere die Zählervariable _counter.

  • _incrementCounter(): Erhöht den Zähler und triggert setState(), um die App neu zu rendern und den neuen Wert an AppState zu übergeben.

  • Im build-Methodenaufruf: Das AppState-Widget umschließt die MaterialApp und übergibt den aktuellen Zustand.

CounterScreen – Zugriff auf den Zustand

Dieses Stateless Widget zeigt den aktuellen Zählerwert an. Es nutzt:

final appState = AppState.of(context);

Dadurch kann es auf die aktuelle Instanz von AppState zugreifen und den counter-Wert abrufen.

FloatingActionButton

Ein einfacher Button, der bei Betätigung die _incrementCounter-Methode aufruft und den Zähler erhöht. Die Funktionsweise der App ist wie in Abbildung 1 dargestellt:. Beim Start zeigt die App den aktuellen Zählerstand an (0). Jedes Mal, wenn der FloatingActionButton (+) gedrückt wird, geschieht Folgendes:

  • Der aktuelle Zustand wird in MyApp aktualisiert.

  • Der AppState wird mit dem neuen Zählerwert rekonstruiert.

  • CounterScreen erkennt die Änderung und wird neu gerendert.

Die aktualisierte Zahl wird auf dem Bildschirm angezeigt.

jax_krypczyk_flutter_4_1

Abb. 1: Zustandsverwaltung mit InheritedWidget/InheritedModel

Provider

Das Provider-Paket ist die offiziell empfohlene Lösung für die Zustandsverwaltung in Flutter. Es baut auf den Mechanismen von InheritedWidget auf, bietet aber ein vereinfachtes API und ermöglicht eine effiziente Verwaltung des Zustands in der gesamten App. Warum sollte man die Provider-Bibliothek verwenden?

  • Einfache Nutzung: Reduziert Boilerplate-Code und vereinfacht die Verwaltung des globalen Zustands.

  • Performance: Nutzt Flutters Build-Optimierungen, um unnötige User-Interface-Updates zu vermeiden.

  • Flexibel: Unterstützt verschiedene Arten von Zuständen (beispielsweise ChangeNotifier, Streams, FutureProvider).

  • Gut integriert: Wird offizielle durch Flutter und eine starke Community unterst...