Nachdem Sie sich mit der deklarativen Programmierung von Benutzeroberflächen und dem Unterschied zwischen kurzlebigem Status und Anwendungsstatus vertraut gemacht haben , können Sie lernen, wie Sie den Anwendungsstatus einfach verwalten.
Wir werden das Paket verwenden
provider. Wenn Sie Flutter noch nicht kennen und keinen zwingenden Grund haben, einen anderen Ansatz zu wählen (Redux, Rx, Hooks usw.), ist dies wahrscheinlich der beste Ansatz, um loszulegen. Das Paket provider ist leicht zu erlernen und erfordert nicht viel Code. Er arbeitet auch mit Konzepten, die in allen anderen Ansätzen anwendbar sind.
Wenn Sie jedoch bereits viel Erfahrung mit der Verwaltung des Status von anderen reaktiven Frameworks haben, können Sie nach anderen Paketen und Tutorials suchen, die auf der Optionsseite aufgeführt sind .
Beispiel
Betrachten Sie die folgende einfache Anwendung als Beispiel.
Die Anwendung verfügt über zwei separate Bildschirme: Katalog und Warenkorb (dargestellt durch Widgets
MyCatalogund MyCartjeweils). In diesem Fall ist dies eine Einkaufs-App, aber Sie können sich dieselbe Struktur in einer einfachen Social-Networking-App vorstellen (ersetzen Sie den Katalog durch "Wand" und den Warenkorb durch "Favoriten").
Der Katalogbildschirm enthält eine anpassbare Anwendungsleiste (
MyAppBar) und eine Bildlaufansicht mehrerer Listenelemente ( MyListItems).
Hier ist die Anwendung in Form eines Widget-Baums:
Wir haben also mindestens 5 Unterklassen
Widget. Viele von ihnen benötigen Zugang, um anzugeben, dass sie nicht besitzen. Zum Beispiel jederMyListItemsollte in der Lage sein, sich in den Warenkorb zu legen. Möglicherweise müssen sie auch überprüfen, ob sich der aktuell angezeigte Artikel im Warenkorb befindet.
Dies bringt uns zu unserer ersten Frage: Wo sollen wir den aktuellen Zustand des Eimers platzieren?
Zunehmender Zustand
In Flutter ist es sinnvoll, den Status über den Widgets zu positionieren, die ihn verwenden.
Wozu? Wenn Sie in deklarativen Frameworks wie Flutter die Benutzeroberfläche ändern möchten, müssen Sie sie neu erstellen. Du kannst nicht einfach gehen und schreiben
MyCart.updateWith(somethingNew). Mit anderen Worten, es ist schwierig, das Ändern des Widgets von außen zu erzwingen, indem die Methode darauf aufgerufen wird Und selbst wenn Sie es zum Laufen bringen könnten, würden Sie gegen das Framework kämpfen, anstatt sich von ihm helfen zu lassen.
// :
void myTapHandler() {
var cartWidget = somehowGetMyCartWidget();
cartWidget.updateWith(item);
}
Selbst wenn Sie den obigen Code zum Laufen bringen, müssen Sie im Widget
MyCartFolgendes beachten :
// :
Widget build(BuildContext context) {
return SomeWidget(
// .
);
}
void updateWith(Item item) {
// - UI.
}
Sie müssen den aktuellen Status der Benutzeroberfläche berücksichtigen und die neuen Daten darauf anwenden. Fehler werden hier nur schwer zu vermeiden sein.
In Flutter erstellen Sie jedes Mal ein neues Widget, wenn sich dessen Inhalt ändert. Anstelle von
MyCart.updateWith(somethingNew)(Methodenaufruf) verwenden Sie MyCart(contents)(Konstruktor). Da Sie nur neue Widgets in den Erstellungsmethoden der übergeordneten Elemente erstellen können, contentsmuss diese , wenn Sie sie ändern möchten, im übergeordneten Element MyCartoder höher sein.
//
void myTapHandler(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
cartModel.add(item);
}
Jetzt
MyCartgibt es nur noch einen Code-Ausführungspfad zum Erstellen einer beliebigen Version der Benutzeroberfläche.
//
Widget build(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
return SomeWidget(
// , .
// ···
);
}
In unserem Beispiel
contentssollte es in sein MyApp. Jedes Mal, wenn es sich ändert, wird der MyCart oben neu erstellt (dazu später mehr). Auf diese MyCartWeise müssen Sie sich keine Gedanken über den Lebenszyklus machen - es wird lediglich festgelegt, was für einen bestimmten Inhalt angezeigt werden soll. Wenn es sich ändert, MyCartverschwindet das alte Widget und wird vollständig durch das neue ersetzt.
Das meinen wir, wenn wir sagen, dass Widgets unveränderlich sind. Sie ändern sich nicht - sie werden ersetzt.
Nachdem wir nun wissen, wo der Bucket-Status abgelegt werden soll, wollen wir sehen, wie wir darauf zugreifen können.
Staatlicher Zugang
Wenn ein Benutzer auf einen der Artikel im Katalog klickt, wird dieser dem Warenkorb hinzugefügt. Aber
MyListItemwie machen wir das, da der Wagen vorbei ist ?
Eine einfache Option besteht darin, einen Rückruf bereitzustellen,
MyListItemder beim Klicken aufgerufen werden kann. Dart-Funktionen sind erstklassige Objekte, sodass Sie sie beliebig übergeben können. Intern können MyCatalogSie also Folgendes definieren:
@override
Widget build(BuildContext context) {
return SomeWidget(
// , .
MyListItem(myTapCallback),
);
}
void myTapCallback(Item item) {
print('user tapped on $item');
}
Dies funktioniert gut, aber für den Status der Anwendung, den Sie von vielen verschiedenen Stellen aus ändern müssen, müssen Sie viele Rückrufe weiterleiten, was ziemlich schnell langweilig wird.
Glücklicherweise verfügt Flutter über Mechanismen, mit denen Widgets ihren Nachkommen Daten und Dienste bereitstellen können (mit anderen Worten, nicht nur ihren Nachkommen, sondern auch allen nachgeschalteten Widgets). Wie Sie von einem Flutter erwarten würde, wo alles , was ein Widget ist , sind diese Mechanismen nur spezielle Arten von Widgets:
InheritedWidget, InheritedNotifier, InheritedModelund andere. Wir werden sie hier nicht beschreiben, da sie leicht von dem abweichen, was wir versuchen zu tun.
Stattdessen verwenden wir ein Paket, das mit Widgets auf niedriger Ebene funktioniert, aber einfach zu verwenden ist. Es heißt
provider.
Mit müssen
providerSie sich keine Gedanken über Rückrufe machen oder InheritedWidgets. Sie müssen jedoch drei Konzepte verstehen:
- ChangeNotifier
- ChangeNotifierProvider
- Verbraucher
ChangeNotifier
ChangeNotifierIst eine einfache Klasse im Flutter SDK, die ihren Listenern eine Benachrichtigung über Statusänderungen bietet. Mit anderen Worten, wenn etwas vorhanden ist ChangeNotifier, können Sie seine Änderungen abonnieren. (Dies ist eine Form von Observable - für diejenigen, die mit dem Begriff nicht vertraut sind.)
ChangeNotifierDies providerist eine Möglichkeit, den Status der Anwendung zusammenzufassen. Für sehr einfache Anwendungen können Sie mit einem auskommen ChangeNotifier. In komplexeren haben Sie mehrere Modelle und daher mehrere ChangeNotifiers. (Sie brauchen nicht zu verwenden , ChangeNotifiermit überhaupt provider, aber diese Klasse ist einfach zu arbeiten.)
In unserem Beispiel Shopping - App wollen wir in den Zustand des Wagens verwalten
ChangeNotifier. Wir erstellen eine neue Klasse, die sie erweitert, zum Beispiel:
class CartModel extends ChangeNotifier {
/// .
final List<Item> _items = [];
/// .
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
/// ( , 42 ).
int get totalPrice => _items.length * 42;
/// [item] . [removeAll] - .
void add(Item item) {
_items.add(item);
// , , .
notifyListeners();
}
/// .
void removeAll() {
_items.clear();
// , , .
notifyListeners();
}
}
Der einzige spezifische Code
ChangeNotifierist der Aufruf notifyListeners(). Rufen Sie diese Methode jedes Mal auf, wenn sich das Modell so ändert, dass es in der Benutzeroberfläche Ihrer Anwendung angezeigt wird. Alles andere CartModelist das Modell selbst und seine Geschäftslogik.
ChangeNotifierist Teil von flutter:foundationund hängt nicht von höheren Klassen in Flutter ab. Es ist einfach zu testen (Sie müssen dafür nicht einmal Widget-Tests verwenden). Hier ist zum Beispiel ein einfacher Komponententest CartModel:
test('adding item increases total cost', () {
final cart = CartModel();
final startingPrice = cart.totalPrice;
cart.addListener(() {
expect(cart.totalPrice, greaterThan(startingPrice));
});
cart.add(Item('Dash'));
});
ChangeNotifierProvider
ChangeNotifierProviderIst ein Widget, das ChangeNotifierseinen untergeordneten Elementen eine Instanz bereitstellt . Es kommt in einem Paket provider.
Wir wissen bereits, wo es platziert werden soll
ChangeNotifierProvider: über den Widgets, die Zugriff darauf benötigen. Falls CartModeles etwas über MyCartund impliziert MyCatalog.
Sie möchten nicht
ChangeNotifierProviderhöher als nötig posten (weil Sie den Bereich nicht verschmutzen möchten). Aber in unserem Fall ist das einzige Widget vorbei MyCartund MyCatalog- es MyApp.
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: MyApp(),
),
);
}
Beachten Sie, dass wir einen Konstruktor definieren, der eine neue Instanz erstellt, die
CartModel. ChangeNotifierProviderintelligent genug ist, um nur dann neu erstellt zu werden, CartModelwenn dies unbedingt erforderlich ist. Es ruft auch automatisch dispose () im CartModel auf, wenn die Instanz nicht mehr benötigt wird.
Wenn Sie mehr als eine Klasse bereitstellen möchten, können Sie Folgendes verwenden
MultiProvider:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CartModel()),
Provider(create: (context) => SomeOtherClass()),
],
child: MyApp(),
),
);
}
Verbraucher
Nachdem es
CartModelden Widgets in unserer Anwendung über die Deklaration ChangeNotifierProvideroben zur Verfügung gestellt wurde, können wir es verwenden.
Dies erfolgt über ein Widget
Consumer.
return Consumer<CartModel>(
builder: (context, cart, child) {
return Text("Total price: ${cart.totalPrice}");
},
);
Wir müssen den Modelltyp angeben, auf den wir zugreifen möchten. In diesem Fall brauchen wir es
CartModel, also schreiben wir Consumer<CartModel>. Wenn Sie generic ( <CartModel>) nicht angeben, kann Ihnen das Paket providernicht weiterhelfen. providerist typbasiert und ohne den Typ wird es nicht verstehen, was Sie wollen.
Das einzige erforderliche Argument für das Widget
Consumerist builder. Builder ist eine Funktion, die bei Änderung aufgerufen wird ChangeNotifier. (Mit anderen Worten, wenn Sie notifyListeners()Ihr Modell aufrufen , werden alle Builder-Methoden aller relevanten Widgets Consumeraufgerufen.)
Der Konstruktor wird mit drei Argumenten aufgerufen. Das erste ist
context, was Sie auch in jeder Build-Methode erhalten.
Das zweite Argument für die Builder-Funktion ist eine Instanz
ChangeNotifier... Darum haben wir von Anfang an gebeten. Mithilfe der Modelldaten können Sie festlegen, wie die Benutzeroberfläche an einem bestimmten Punkt aussehen soll.
Das dritte Argument ist
child, dass es für die Optimierung benötigt wird. Wenn Sie einen großen Widget-Teilbaum unter sich haben Consumer, der sich nicht ändert, wenn sich das Modell ändert, können Sie ihn einmal erstellen und über den Builder abrufen.
return Consumer<CartModel>(
builder: (context, cart, child) => Stack(
children: [
// SomeExhibitedWidget, .
child,
Text("Total price: ${cart.totalPrice}"),
],
),
// .
child: SomeExpensiveWidget(),
);
Platzieren Sie Ihre Consumer-Widgets am besten so tief wie möglich im Baum. Sie möchten keine großen Teile der Benutzeroberfläche neu erstellen, nur weil sich einige Details irgendwo geändert haben.
//
return Consumer<CartModel>(
builder: (context, cart, child) {
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Text('Total price: ${cart.totalPrice}'),
),
);
},
);
Stattdessen:
//
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total price: ${cart.totalPrice}');
},
),
),
);
Provider.of
Manchmal benötigen Sie die Daten im Modell nicht wirklich, um die Benutzeroberfläche zu ändern, aber Sie benötigen dennoch Zugriff darauf. Mit einer Schaltfläche
ClearCartkann der Benutzer beispielsweise alles aus dem Warenkorb entfernen. Es ist nicht erforderlich, den Inhalt des Warenkorbs anzuzeigen. Rufen Sie einfach die Methode auf clear().
Wir könnten es
Consumer<CartModel>dafür verwenden, aber das wäre verschwenderisch. Wir würden das Framework bitten, das Widget neu zu erstellen, das nicht neu erstellt werden muss.
Für diesen Anwendungsfall können wir
Provider.ofmit dem Parametersatz listenverwenden false.
Provider.of<CartModel>(context, listen: false).removeAll();
Wenn Sie die obige Zeile in der Erstellungsmethode verwenden, wird dieses Widget beim Aufruf nicht neu erstellt
notifyListeners .
Alles zusammenfügen
Sie können sich das in diesem Artikel beschriebene Beispiel ansehen. Wenn Sie etwas Einfacheres benötigen, sehen Sie sich an, wie eine einfache Counter-Anwendung aussieht, die mit dem Anbieter erstellt wurde .
Wenn Sie bereit sind, mit sich
providerselbst zu spielen , denken Sie daran, zuerst die Abhängigkeit zu Ihrer hinzuzufügen pubspec.yaml.
name: my_name
description: Blah blah blah.
# ...
dependencies:
flutter:
sdk: flutter
provider: ^3.0.0
dev_dependencies:
# ...
Jetzt kannst du
'package:provider/provider.dart'; und fang an zu bauen ...
