So verwenden Sie das Provider-Muster in Flutter

In diesem Beitrag werfen wir einen Blick auf das Anbietermuster in Flutter. Einige andere Muster, wie z. B. die BLoC-Architektur, verwenden das Anbietermuster intern. Das Anbietermuster ist jedoch viel einfacher zu erlernen und enthält viel weniger Boilerplate-Code.

In diesem Beitrag nehmen wir die von Flutter bereitgestellte Standard-Counter-App und überarbeiten sie, um das Provider-Muster zu verwenden.

Wenn Sie wissen möchten, was das Flutter-Team von Google zum Anbietermuster zu sagen hat, lesen Sie diesen Vortrag von 2019.

Wenn Sie mehr über BLoC Architecture erfahren möchten, lesen Sie es hier.

Loslegen

Erstellen Sie ein neues Flutter-Projekt und benennen Sie es nach Ihren Wünschen.

Zuerst müssen wir alle Kommentare entfernen, damit wir eine saubere Tafel haben, mit der wir arbeiten können:

import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } 

Fügen Sie nun die Abhängigkeit für das Anbietermuster in die pubspec.yamlDatei ein. Zum Zeitpunkt des Schreibens ist die neueste Version 4.1.2.

So sieht Ihre pubspec.yamlDatei jetzt aus:

name: provider_pattern_explained description: A new Flutter project. publish_to: 'none' version: 1.0.0+1 environment: sdk: ">=2.7.0 <3.0.0" dependencies: flutter: sdk: flutter provider: ^4.1.2 cupertino_icons: ^0.1.3 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true 

Die Standard-App ist im Grunde ein Stateful-Widget, das jedes Mal neu erstellt wird, wenn Sie auf das klicken FloatingActionButton(was aufruft setState()).

Aber jetzt werden wir es in ein zustandsloses Widget konvertieren.

Provider erstellen

Lassen Sie uns fortfahren und unseren Anbieter erstellen. Dies wird die einzige Quelle der Wahrheit für unsere App sein. Hier speichern wir unseren Status, in diesem Fall die aktuelle Anzahl.

Erstellen Sie eine Klasse mit dem Namen Counterund fügen Sie die countVariable hinzu:

import 'package:flutter/material.dart'; class Counter { var _count = 0; } 

Erweitern Sie ChangeNotifierdas material.dartPaket, um es in eine Anbieterklasse zu konvertieren . Dies stellt uns die notifyListeners()Methode zur Verfügung und benachrichtigt alle Listener, wenn wir einen Wert ändern.

Fügen Sie nun eine Methode hinzu, um den Zähler zu erhöhen:

import 'package:flutter/material.dart'; class Counter extends ChangeNotifier { var _count = 0; void incrementCounter() { _count += 1; } } 

Am Ende dieser Methode werden wir aufrufen notifyListeners(). Dies löst eine Änderung in der gesamten App für das Widget aus, das es gerade hört.

Das ist das Schöne am Anbietermuster in Flutter - Sie müssen sich nicht um das manuelle Versenden an Streams kümmern.

Erstellen Sie schließlich einen Getter, um den Zählerwert zurückzugeben. Wir werden dies verwenden, um den neuesten Wert anzuzeigen:

import 'package:flutter/material.dart'; class Counter extends ChangeNotifier { var _count = 0; int get getCounter { return _count; } void incrementCounter() { _count += 1; notifyListeners(); } } 

Tastenklicks anhören

Nachdem wir den Anbieter eingerichtet haben, können wir ihn in unserem Haupt-Widget verwenden.

Lassen Sie uns zunächst MyHomePagein ein zustandsloses Widget anstatt in ein zustandsbehaftetes Widget konvertieren . Wir müssen den setState()Anruf entfernen , da dies nur in a StatefulWidget:

import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatelessWidget { int _counter = 0; final String title; MyHomePage({this.title}); void _incrementCounter() {} @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } 

Nachdem dies erledigt ist, können wir jetzt das Provider-Muster in Flutter verwenden, um den Zählerwert festzulegen und abzurufen. Bei jedem Klick auf die Schaltfläche müssen wir den Zählerwert um 1 erhöhen.

Fügen Sie in der _incrementCounterMethode (die beim Drücken der Taste aufgerufen wird) diese Zeile hinzu:

Provider.of(context, listen: false).incrementCounter();

Was hier passiert ist, dass Sie Flutter gebeten haben, in den Widget-Baum zu gehen und den ersten Ort zu finden, an dem er Counterbereitgestellt wird. (Ich werde Ihnen im nächsten Abschnitt erklären, wie Sie es bereitstellen können.) Dies ist, was Provider.of()tut.

Die Generika (Werte im Inneren Klammern) teilen Flutter mit, nach welcher Art von Anbieter gesucht werden soll. Dann geht Flutter durch den Widget-Baum, bis der angegebene Wert gefunden ist. Wenn der Wert nirgendwo angegeben wird, wird eine Ausnahme ausgelöst.

Sobald Sie den Anbieter haben, können Sie eine beliebige Methode aufrufen. Hier nennen wir unsere incrementCounterMethode.

Wir brauchen aber auch einen Kontext, also akzeptieren wir den Kontext als Argument und ändern die onPressedMethode, um auch den Kontext zu übergeben:

void _incrementCounter(BuildContext context) { Provider.of(context, listen: false).incrementCounter(); } 

Hinweis: Wir haben listen auf false gesetzt, da wir hier keine Werte anhören müssen. Wir senden nur eine auszuführende Aktion.

Bereitstellung des Anbieters

Das Anbietermuster in Flutter sucht nach dem neuesten bereitgestellten Wert. Das folgende Diagramm hilft Ihnen beim besseren Verständnis.

In diesem Diagramm steht das GRÜNE Objekt A den übrigen Elementen darunter zur Verfügung, dh B, C, D, E und F.

Angenommen, wir möchten der App einige Funktionen hinzufügen und einen anderen Anbieter erstellen, Z. Z wird von E und F benötigt.

Wo kann man das am besten hinzufügen?

Wir können es der Wurzel über A hinzufügen . Das würde funktionieren:

Diese Methode ist jedoch nicht sehr effizient.

Flutter durchläuft alle oben genannten Widgets und geht schließlich zum Stammverzeichnis. Wenn Sie sehr lange Widget-Bäume haben - was Sie definitiv in einer Produktions-App tun werden -, ist es keine gute Idee, alles an der Wurzel zu platzieren.

Stattdessen können wir uns den gemeinsamen Nenner von E und F ansehen. Das ist C. Wenn wir also Z direkt über E und F setzen, würde es funktionieren.

But what if we want to add another object X that'srequired by E and F? We'll do the same thing. But notice how the tree keeps growing.

There’s a better way to manage that. What if we provide all the objects at one level?

This is perfect, and is how we’ll eventually implement our provider pattern in Flutter. We’ll make use of something called MultiProviderwhich lets us declare multiple providers at one level.

We'll get MultiProvider to wrap the MaterialApp widget:

class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider.value( value: Counter(), ), ], child: MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: "AndroidVille Provider Pattern"), ), ); } } 

With this, we’ve provided the provider to our widget tree and can use it anywhere below this level in the tree.

There’s just one more thing left: we need to update the value that's displayed.

Updating the text

To update the text, get the provider in the build function of your MyHomePage widget. We’ll use the getter we created to get the latest value.

Then just add this value to the text widget below.

And we’re done! This is how your final main.dart file should look:

import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:provider_pattern_explained/counter.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider.value( value: Counter(), ), ], child: MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: "AndroidVille Provider Pattern"), ), ); } } class MyHomePage extends StatelessWidget { final String title; MyHomePage({this.title}); void _incrementCounter(BuildContext context) { Provider.of(context, listen: false).incrementCounter(); } @override Widget build(BuildContext context) { var counter = Provider.of(context).getCounter; return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), Text( '$counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => _incrementCounter(context), tooltip: 'Increment', child: Icon(Icons.add), ), ); } } 

Note: We haven’t set listen:false in this case because we want to listen to any updates in the count value.

Here's the source code on GitHub if you want to have a look: //github.com/Ayusch/Flutter-Provider-Pattern.

Let me know if you have any issues.

Welcome to AndroidVille :)

AndroidVille is a community of Mobile Developers where we share knowledge related to Android Development, Flutter Development, React Native Tutorials, Java, Kotlin and much more.

Click on this link to join the AndroidVille SLACK workspace. It’s absolutely free!

If you liked this article, feel free to share it on Facebook or LinkedIn. You can follow me on LinkedIn, Twitter, Quora, and Medium where I answer questions related to mobile development, Android, and Flutter.