Flattern BloC-Muster + Provider + Tests + erinnere mich an den Zustand

Dieser Artikel ist aus der Veröffentlichung „ BLoC-Muster mit einem einfachen Beispiel “ hervorgegangen, in der wir herausgefunden haben, was dieses Muster ist und wie es in einem klassischen einfachen Gegenbeispiel angewendet wird .


Nach den Kommentaren und nach meinem besten Verständnis habe ich beschlossen, eine Bewerbung zu schreiben, in der Antworten auf Fragen eingehen:


  1. So übertragen Sie den Status der Klasse, in der sich der BloC befindet, in der gesamten Anwendung
  2. Wie schreibt man Tests für dieses Muster?
  3. (zusätzliche Frage) So behalten Sie den Datenzustand zwischen den Anwendungsstarts bei, während Sie im BLoC-Muster bleiben

Unten ist ein animashka des resultierenden Beispiels und unter dem Schnitt ist eine Nachbesprechung :)


Und am Ende des Artikels ist ein interessantes Problem, wie die Anwendung zum Anwenden des Entprellen-Operators aus dem ReactiveX- Muster geändert werden kann (genauer gesagt, ReactiveX ist eine Erweiterung des Observer-Musters).




Beschreibung der Anwendung und Basiscode


Nicht verbunden mit BLoC und Provider


  1. Die Anwendung verfügt über Schaltflächen + - und Wischen, die diese Schaltflächen duplizieren
  2. Animation über eingebauten Flatter-Mix - TickerProviderStateMixin

Verbunden mit BLoC und Provider


  1. Zwei Bildschirme - auf dem ersten streichen wir, auf dem zweiten werden die Zähleränderungen angezeigt
  2. Wir schreiben den Status in den permanenten Speicher des Telefons (iOS & Android, Paket https://pub.dev/packages/shared_preferences )
  3. Das Schreiben und Lesen von Informationen aus dem permanenten Speicher erfolgt asynchron, wir tun dies auch über BLoC

Wir schreiben eine Bewerbung


Wie aus der Definition des BLoC-Musters hervorgeht, besteht unsere Aufgabe darin, alle Logik aus Widgets zu entfernen und mit Daten über eine Klasse zu arbeiten, in der alle Ein- und Ausgaben Streams sind.


Da die Klasse, in der sich BLoC befindet, auf verschiedenen Bildschirmen verwendet wird, müssen wir gleichzeitig das aus dieser Klasse erstellte Objekt in die gesamte Anwendung übertragen.


Hierfür gibt es verschiedene Methoden, nämlich:


  1. Durchlaufen Klassenbauer den sogenannten Lifting State Up . Wir werden es nicht verwenden, da es sich als sehr verwirrend herausstellt. Verfolgen Sie dann nicht die Zustandsübertragungen.
  2. Machen Sie aus Klasse, wo wir BLoC Singleton haben und importieren Sie es, wo wir brauchen. Es ist einfach und bequem, aber aus meiner rein persönlichen Sicht kompliziert es den Klassenkonstruktor und verwirrt die Logik ein wenig.
  3. Verwenden Sie das Provider-Paket, das vom Flutter-Team für die Statusverwaltung empfohlen wird. Sehen Sie sich das Video an

In diesem Beispiel verwenden wir den Provider - um ein Beispiel für alle Methoden zu geben, die nicht genügend Stärke hatten :)


Allgemeine Struktur


Wir haben also eine Klasse


class SwipesBloc { // some stuff } 

und damit auf ein aus dieser Klasse erstelltes Objekt im gesamten Widget-Baum zugegriffen werden kann, definieren wir auf einer bestimmten Ebene von Anwendungs-Widgets einen Anbieter aus dieser Klasse. Ich habe dies ganz oben im Widget-Baum getan, aber es ist am besten, es auf der niedrigstmöglichen Ebene zu tun.


 class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ Provider<SwipesBloc>(create: (_) => SwipesBloc()), ], child: MaterialApp( title: 'Swipe BLoC + Provider', 

Nach dem Hinzufügen dieses schönen Designs zu einem Widget am unteren Ende des Baums steht uns ein Objekt mit allen Daten zur Verfügung. Details zur Arbeit mit Provider hier und hier .


Als Nächstes müssen wir sicherstellen, dass beim Klicken auf die Schaltfläche oder beim Streichen alle Daten in den Stream übertragen werden und dann auf allen Bildschirmen die Daten aus demselben Stream aktualisiert werden.


Klasse für BLoC


Dazu erstellen wir eine BLoC-Klasse, in der wir nicht nur die Streams, sondern auch den Empfang und die Aufzeichnung des Status aus dem permanenten Speicher des Telefons beschreiben.


 import 'dart:async'; import 'package:rxdart/rxdart.dart'; import 'package:shared_preferences/shared_preferences.dart'; class SwipesBloc { Future<SharedPreferences> prefs = SharedPreferences.getInstance(); int _counter; SwipesBloc() { prefs.then((val) { if (val.get('count') != null) { _counter = val.getInt('count') ?? 1; } else { _counter = 1; } _actionController.stream.listen(_changeStream); _addValue.add(_counter); }); } final _counterStream = BehaviorSubject<int>.seeded(1); Stream get pressedCount => _counterStream.stream; void get resetCount => _actionController.sink.add(null); Sink get _addValue => _counterStream.sink; StreamController _actionController = StreamController(); StreamSink get incrementCounter => _actionController.sink; void _changeStream(data) async { if (data == null) { _counter = 1; } else { _counter = _counter + data; } _addValue.add(_counter); prefs.then((val) { val.setInt('count', _counter); }); } void dispose() { _counterStream.close(); _actionController.close(); } } 

Wenn wir uns diese Klasse genau ansehen, werden wir sehen, dass:


  1. Alle extern verfügbaren Eigenschaften sind die Ein- und Ausgänge von Streams.
  2. Beim ersten Start im Designer versuchen wir, Daten aus dem permanenten Speicher des Telefons abzurufen.
  3. Praktisch im permanenten Speicher des Telefons aufgezeichnet

Kleine Aufgaben zum besseren Verständnis:


  • Das Entfernen eines Teils des Codes aus .then aus dem Konstruktor ist schöner, um eine separate Methode zu erstellen.
  • Versuchen Sie, diese Klasse ohne einen Anbieter als Singleton zu implementieren

Empfangen und senden Sie Daten in der Anwendung


Jetzt müssen wir Daten an Stream übertragen, wenn wir auf Schaltflächen klicken oder wischen, und diese Daten auf der Karte und auf einem separaten Bildschirm abrufen.


Es gibt verschiedene Möglichkeiten, dies zu tun. Ich habe die klassische Option gewählt. Wir verpacken die Teile des Baums, in denen Sie Daten empfangen / an Consumer übertragen müssen


 return Scaffold( body: Consumer<SwipesBloc>( builder: (context, _swipesBloc, child) { return StreamBuilder<int>( stream: _swipesBloc.pressedCount, builder: (context, snapshot) { String counterValue = snapshot.data.toString(); return Stack( children: <Widget>[ Container( 

Na dann Daten holen
_swipesBloc.pressedCount,


Datenübertragung
_swipesBloc.incrementCounter.add(1);


Das ist alles, wir haben einen klaren und erweiterbaren Code in den Regeln des BLoC-Musters.


Arbeitsbeispiel


Tests


Sie können Widgets testen, Sie können Mokas erstellen, Sie können e2e.


Wir werden die Widgets testen und die Anwendung mit einer Überprüfung der Funktionsweise der Zählererhöhung ausführen. Informationen zu Tests hier und hier .


Widget testen


Wenn wir synchrone Daten hätten, könnten wir alles mit Widgets testen. In unserem Fall können wir nur überprüfen, wie die Widgets erstellt wurden und wie die Initialisierung verlief.


Der Code ist hier , im Code gibt es Versuche, den Zählerstand nach dem Anklicken zu überprüfen - es kommt zu einem Fehler, da die Daten über BLoC gehen.


Verwenden Sie den Befehl, um den Test auszuführen
flutter test


Integrationstests


In dieser Testoption läuft die Anwendung auf dem Emulator, wir können Tasten drücken, wischen und überprüfen, was als Ergebnis passiert ist.


Dazu erstellen wir 2 Dateien:


test_driver / app.dart
test_driver / app_test.dart


Zum einen verbinden wir, was benötigt wird, und zum anderen direkt die Tests. Ich habe zum Beispiel folgende Prüfungen durchgeführt:


  • Ausgangszustand
  • Zählerstände nach Knopfdruck

 import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; void main() { group( 'park-flutter app', () { final counterTextFinder = find.byValueKey('counterKey'); final buttonFinder = find.byValueKey('incrementPlusButton'); FlutterDriver driver; setUpAll(() async { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) { driver.close(); } }); test('test init value', () async { expect(await driver.getText(counterTextFinder), ^_^quot quot^_^); }); test('test + 1 value after tapped', () async { await driver.tap(buttonFinder); // Then, verify the counter text is incremented by 1. expect(await driver.getText(counterTextFinder), ^_^quot quot^_^); }); }, ); } 

Code dort


Verwenden Sie den Befehl, um den Test auszuführen
flutter drive --target=test_driver/app.dart


Herausforderung


Nur um dein Verständnis zu vertiefen. In modernen Anwendungen (Sites) wird häufig die Debounce-Funktion von ReactiveX verwendet.


Zum Beispiel:


  1. Ein Wort wird in die Suchleiste eingegeben und ein Hinweis wird nur dann angezeigt, wenn der Abstand zwischen den Buchstaben mehr als 2 Sekunden beträgt
  2. Wenn Likes eingegeben werden, können Sie 10-mal pro Sekunde klicken. Das Schreiben in die Datenbank erfolgt, wenn die Lücke in den Klicks länger als 2-3 Sekunden war
  3. … usw.

Aufgabe: Ändern der Ziffer nur, wenn zwischen den Drücken von + oder - mehr als 2 Sekunden vergehen. Bearbeiten Sie dazu nur die BLoC-Klasse, der Rest des Codes sollte gleich bleiben.




Das ist alles. Wenn etwas schief oder falsch ist, hier oder auf Github korrigieren , versuchen Sie, das Ideal zu erreichen :)


Gute Kodierung an alle!

Source: https://habr.com/ru/post/de485002/


All Articles