Ok Ok. Zugegeben, der Titel ist etwas zu harsch. Ich musste mich jedoch arbeitsbedingt mit Flutter Riverpod beschäftigen und ich hab es gehasst. Dann hab ich mir es mal etwas genauer unter die Lupe genommen und … nunja war immernoch nicht ganz so überzeugt. Aber schauen wir uns das mal gemeinsam an.
Als Entwickler bist du sicherlich mit der Bedeutung von State Management System bei der Entwicklung einer App vertraut. Doch die Umsetzung eines State Management Systems ist alles andere als einfach. Flutter Riverpod ist eins der bekannteren Lösungen für sowas, was sogar auf der offiziellen Flutter Seite mit aufgelistet wird.
Falls du dich alternativ für Flutter Bloc interessierst, dann schau hier mein ausführlichen Blog Eintrag zu Flutter Bloc dazu an. Und vielen Dank an Andrea und den Flutter Riverpod Blog Post von ihm für viele Inspirationen. Außerdem gibt es auch ein Vergleich von Bloc und Riverpod. Schau auch da gerne vorbei.
Die Fakten zu Flutter Riverpod
Flutter Riverpod wurde von Remi Rousselet entwickelt. Es basiert auf dem Provider-Paket, geht aber einen Schritt weiter und bietet das Konzept der Dependecy Injection. Mit Rivepod kannst du auf all deinen Providern in der ganzen App drauf zugreifen, was mehrere Vorteile für dich als Flutter Entwickler bietet. Dazu gehören:
-
Einfach zu verwenden: Mit Flutter Riverpod kannst du die Abhängigkeiten deiner App definieren und sie in deiner gesamten App zugänglich machen.
-
Unabhängig: Riverpod stellt alle Provider über die gesamte App zur Verfügung. Wenn deine App wächst, kann Riverpod auch komplexere Zustandsverwaltungsszenarien bewältigen.
-
Testbar: Riverpod erleichtert das Testen der Zustandsverwaltung deiner App, indem es dir ermöglicht, deine Abhängigkeiten zu mocken.
-
Leistung: Riverpod basiert auf dem Provider-Paket, das für seine hervorragende Leistung bekannt ist.
Warum nicht einfach Provider nutzen?
Kannst du gerne machen. Nur gibt es da einige Probleme, beim Zugriff über parallele Widgets hinweg. Schau dir dazu diese Übersicht an.
Solange sich der Provider im Subtree befindet, ist das alles kein Problem. Sobald man jedoch über ein Widget hinaus parallel gehen möchte, stellt dies für den herkömmlichen Provider ein Problem dar. Aus diesem Grund hat Riverpod beschlossen, einfach alle Provider jederzeit zur Verfügung zu stellen.
Wie funktioniert Flutter Riverpod?
Flutter Riverpod funktioniert mit dem Konzept der „Provider“. Provider sind Objekte, die Daten oder Funktionalität halten, auf die in deiner gesamten App zugegriffen werden kann. Provider können entweder „Leseprovider“ oder „Schreibprovider“ sein.
Leseprovider: Diese Provider halten Daten, auf die in deiner gesamten App zugegriffen werden kann, aber nicht geändert werden kann.
Schreibprovider: Diese Provider halten Daten, auf die in deiner gesamten App zugegriffen und geändert werden kann.
Erste Schritte mit Riverpod
Bevor wir beginnen, müssen wir uns zuerst darauf einigen, welches Paket wir installieren möchten. Riverpod bietet hier drei verschiedene Pakete an. Für uns ist jedoch nur das „flutter_riverpod“ Paket relevant.
App Typ | Paket Name | Beschreibung |
---|---|---|
Flutter + flutter_hooks | hooks_riverpod | Erlaubt die Nutzung von beiden Paketen flutter_hooks und Riverpod. |
Flutter only | flutter_riverpod | Ein einfacher Weg um Riverpod in Flutter Applikationen zu verwenden. |
Dart only (No Flutter) | riverpod | Eine Riverpod Version ohne die Flutter Klassen. |
Um mit Flutter Riverpod zu beginnen installiere das Package über folgenden Befehl:
flutter pub add riverpod
pubspec.yaml
-Datei hinzu und führe flutter pub get
aus
dependencies:
flutter_riverpod: ^0.14.0+3
Sobald wir Riverpod installiert haben, fügen wir den ProviderScope
unserer main.dart
hinzu. Dadurch erhalten wir Zugriff auf alle Provider innerhalb der App.
void main() {
runApp(ProviderScope(
child: MyApp(),
));
}
Jetzt die eigentliche Arbeit - Provider
Schauen wir uns mal ein Hello World Provider an:
final helloWorldProvider = Provider((ref) {
return 'Hello world';
});
class HelloWorldWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final helloWorld = ref.watch(helloWorldProvider);
return Text(helloWorld);
}
}
Provider können in der Widget-Datei selbst implementiert werden oder in einer separaten Datei, was ich definitiv eher empfehle. Für dieses Beispiel lassen wir den Provider in derselben Datei.
Um auf die Provider zuzugreifen, verwenden wir das ConsumerWidget anstelle eines StatelessWidget. Dieses speziell für Riverpod erstellte Widget hat neben dem context Parameter nun auch einen zusätzlichen ref Parameter. Dieser ref Parameter dient als Schnittstelle, um auf alle Provider zuzugreifen zu können.
Jetzt gibt es drei verschiedene Arten Daten von einem Provider zu erhalten.
// Gibt einmalig die Daten zurück
final String helloWorld = ref.read(helloWorldProvider);
// Gibt die Daten nach jedem Update zurück
final String helloWorld = ref.watch(helloWorldProvider);
// Ähnlich wie 'watch'.
// Unterschied: Daten werden über ein Callback zurückgegeben.
ref.listen(helloWorldProvider, (previous, next) {
print(next)
});
Das erste Beispiel sollte hier klar sein. Wenn ich einmalig den Wert eines Providers benötige, verwenden wir ‚read‘. Ein Anwendungsbeispiel dafür wäre in der initState Methode oder in der onPressed Methode eines Buttons.
Die Methode ‚watch‘ gibt den Wert jedes Mal zurück, nachdem sich dieser geändert hat. ‚Watch‘ wird hauptsächlich in der build Methode verwendet, um die UI mit dem aktuellen Wert des Providers zu aktualisieren.
Dann haben wir noch die listener Methode. Diese hat einen void Rückgabewert, aber ein Callback mit dem aktuellen und vorherigen Wert des Providers. Anwendungsbeispiele dafür wären das Anzeigen von Snackbars oder ähnlichen Benachrichtigungen, sowie das triggern von anderen Events.
Ja, alles cool. Aber was ist mit Futures?
Schön, dass du fragst. Ich hab da mal was vorbereitet.
final cocktailFutureProvider = FutureProvider.autoDispose>(
(ref) {
final cocktailRepository = ref.watch(cocktailRepositoryProvider);
return cocktailRepository.getRandomCocktails();
});
final cocktailRepositoryProvider = Provider((ref) {
return CocktailRepository(); // declared elsewhere
});
ref.watch(cocktailFutureProvider).when(
loading: () {
return CircularProgressIndicator();
},
error: (error, stack) {
return Text(error);
},
data: (cocktails) {
return Text('Cocktails fetched done!');
},
);
Dieses Code Snippet stammt aus meinem State-Management-Repo. Wenn es darum geht, Daten von einer API zu laden und mit Futures zu arbeiten, bietet Riverpod den FutureProvider an.
In der build Methode hat die ‚watch‘ Methode hierfür ein zusätzliches Feature. Mit ‚when‘ ist es möglich, die verschiedenen Zustände des asynchronen Aufrufs in der UI anzuzeigen. Mit ‚Loading‘ für die Ladeanimation, mit ‚error‘ für die Fehlerbenachrichtigung und mit ‚data‘ für das erfolgreiche Laden und Anzeigen der Daten.
Aber ist Riverpod jetzt wirklich so schlecht?
Naja, nee. Ich bin aber immer noch mehr Fan von Flutter Bloc. Aber warum? Zum einen, weil der Mensch ein Gewohnheitstier ist und ich schon mehrere Projekte mit Bloc umgesetzt habe. Andererseits finde ich es irgendwie gruselig, dass ich meine ganzen Widgets zu ConsumerWidgets umschreiben muss.
Dann habe ich ein Problem damit, dass alle Provider von überall aus erreichbar sind. Ich bin eher ein Fan davon, in sich geschlossene Widgets zu haben.
Aber wenn du mehr Erfahrung mit Riverpod hast, dann schreib mir gerne einen Kommentar dazu. Vielleicht belehrst du mich eines Besseren. Möglicherweise sehe ich die Vorteile noch nicht richtig.
Ansonsten kann ich dir meinen Beitrag zu Flutter Bloc empfehlen, um ein alternatives State-Management-System kennenzulernen.