Plattformübergreifend entwickeln mit Flutter – Teil 4
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.
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.
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.
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),
),
);
}
}
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.
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.
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.
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.
Abb. 1: Zustandsverwaltung mit InheritedWidget/InheritedModel
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...