رفرفة نمط بلوك + مزود + اختبارات + تذكر الدولة

نشأت هذه المقالة من منشور " نموذج BLoC مع مثال بسيط " حيث اكتشفنا ماهية هذا النمط وكيفية تطبيقه في مثال بسيط بسيط.


وفقًا للتعليقات ولأفهم أفضل ما لدي ، قررت أن أحاول كتابة طلب يتم فيه تلقي إجابات عن الأسئلة:


  1. كيفية نقل حالة الفئة التي يوجد بها BloC خلال التطبيق
  2. كيفية كتابة اختبارات لهذا النمط
  3. (سؤال إضافي) كيفية الحفاظ على حالة البيانات بين عمليات تشغيل التطبيق مع البقاء داخل نمط BLoC

يوجد أدناه animashka للمثال الناتج ، وتحت القصاص استخلاص معلومات :)


وفي نهاية المقال ، تتمثل المشكلة المثيرة للاهتمام في كيفية تعديل تطبيق تطبيق عامل التشغيل Debounce من نمط ReactiveX (وبشكل أكثر دقة ، يعد reactiveX امتدادًا لنموذج Observer)




وصف التطبيق والرمز الأساسي


لا علاقة لها بلوك ومزود


  1. يحتوي التطبيق على أزرار + - وانتقد تعمل على تكرار هذه الأزرار
  2. الرسوم المتحركة التي تتم عبر رفرفة مزيج مدمج - TickerProviderStateMixin

مرتبطة بلوك ومزود


  1. شاشتان - في أول شاشة ننتقدها ، في الثاني يتم عرض تغييرات العداد
  2. نكتب الحالة إلى التخزين الدائم للهاتف (iOS و Android ، الحزمة https://pub.dev/packages/shared_preferences )
  3. كتابة وقراءة المعلومات من التخزين الثابت غير متزامنة ، ونحن نفعل ذلك أيضًا من خلال BLoC

نحن نكتب الطلب


على النحو التالي من تعريف نمط BLoC ، تتمثل مهمتنا في إزالة كل المنطق من عناصر واجهة التعامل والعمل مع البيانات من خلال فئة تكون فيها جميع المدخلات والمخرجات عبارة عن تدفقات.


في الوقت نفسه ، نظرًا لأن الفئة التي يقع فيها موقع BLoC تستخدم على شاشات مختلفة ، نحتاج إلى نقل الكائن الذي تم إنشاؤه من هذه الفئة عبر التطبيق.


هناك طرق مختلفة لهذا ، وهي:


  1. تمر من خلال صانعي الصف ، ما يسمى حالة الرفع لأعلى . لن نستخدمها ، حيث أنها تبدو مربكة للغاية ، فلا تتبع عمليات نقل الحالة.
  2. جعل من الصف حيث لدينا BLOC المفردة واستيرادها حيث نحتاج. إنه بسيط ومريح ، لكن من وجهة نظري الشخصية البحتة ، يعقد مُنشئ الفصل ويخلط المنطق بعض الشيء.
  3. استخدم حزمة المزود - الذي يوصى به فريق Flutter لإدارة الحالة. شاهد الفيديو

في هذا المثال ، سوف نستخدم موفر - لإعطاء مثال على جميع الأساليب لم يكن لديك ما يكفي من القوة :)


الهيكل العام


لذلك لدينا فئة


class SwipesBloc { // some stuff } 

وبحيث يمكن الوصول إلى كائن تم إنشاؤه من هذه الفئة عبر شجرة عنصر واجهة المستخدم ، فإننا ، على مستوى معين من عناصر واجهة تعامل التطبيق ، نقوم بتحديد مزود من هذه الفئة. لقد قمت بذلك في أعلى شجرة القطعة ، ولكن من الأفضل القيام بذلك على أقل مستوى ممكن.


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

بعد إضافة هذا التصميم الجميل إلى أي عنصر واجهة مستخدم في أسفل الشجرة ، يتوفر لنا كائن به جميع البيانات. تفاصيل كيفية العمل مع موفر هنا وهنا .


بعد ذلك ، نحتاج إلى التأكد من أنه عند النقر فوق الزر أو التمرير السريع ، يتم نقل جميع البيانات إلى البث المباشر ، ثم ، على جميع الشاشات ، يتم تحديث البيانات من نفس البث.


فئة ل BLOC


للقيام بذلك ، نقوم بإنشاء فئة BLoC لا تصف فيها التدفقات فحسب ، ولكن أيضًا استلام وتسجيل الحالة من وحدة التخزين الدائمة للهاتف.


 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(); } } 

إذا نظرنا بعناية إلى هذا الفصل ، فسنرى ما يلي:


  1. أي الخصائص المتاحة خارجيا هي المدخلات والمخرجات من التدفقات.
  2. عند التشغيل لأول مرة في المصمم ، نحاول الحصول على البيانات من وحدة التخزين الدائمة للهاتف.
  3. سجلت مريح في التخزين الدائم للهاتف

المهام الصغيرة لفهم أفضل:


  • إزالة جزء من الكود من. ثم من المُنشئ هو أجمل طريقة منفصلة.
  • حاول تطبيق هذه الفئة بدون مزود مثل Singleton

تلقي ونقل البيانات في التطبيق


نحن الآن بحاجة إلى نقل البيانات إلى البث عند النقر على الأزرار أو السحب والحصول على هذه البيانات على البطاقة وعلى شاشة منفصلة.


هناك خيارات مختلفة لكيفية القيام بذلك ، لقد اخترت الخيار الكلاسيكي ، فنحن نلف تلك الأجزاء من الشجرة حيث تحتاج إلى استلام / نقل البيانات إلى المستهلك


 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( 

حسنا ، ثم الحصول على البيانات
_swipesBloc.pressedCount,


نقل البيانات
_swipesBloc.incrementCounter.add(1);


هذا كل شيء ، حصلنا على رمز واضح وقابل للتوسعة في قواعد نموذج BLoC.


مثال العمل


اختبارات


يمكنك اختبار الحاجيات ، يمكنك جعل mokas ، يمكنك e2e.


سنقوم باختبار التطبيقات المصغّرة وتشغيل التطبيق مع فحص كيفية عمل زيادة العداد. معلومات عن الاختبارات هنا وهنا .


اختبار القطعة


إذا كان لدينا بيانات متزامنة ، فيمكننا اختبار كل شيء باستخدام عناصر واجهة المستخدم. في حالتنا ، يمكننا فقط التحقق من كيفية إنشاء التطبيقات المصغّرة وكيف تمت التهيئة.


الرمز هنا ، يوجد في الكود محاولات للتحقق من زيادة العداد بعد النقر - إنه يعطي خطأ ، لأن البيانات تمر عبر BLoC.


لتشغيل الاختبار ، استخدم الأمر
flutter test


اختبارات التكامل


في هذا الخيار للاختبار ، يعمل التطبيق على المحاكي ، يمكننا الضغط على الأزرار والسحب والتحقق مما حدث كنتيجة لذلك.


للقيام بذلك ، نقوم بإنشاء ملفين:


test_driver / app.dart
test_driver / app_test.dart


في الأول ، نربط ما هو مطلوب ، وفي الثانية ، اختبارات مباشرة. على سبيل المثال ، فعلت الشيكات:


  • الحالة الأولية
  • مكافحة الزيادات بعد الضغط على زر

 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^_^); }); }, ); } 

كود هناك


لتشغيل الاختبار ، استخدم الأمر
flutter drive --target=test_driver/app.dart


مهمة


فقط لتعميق تفهمك. في التطبيقات الحديثة (المواقع) ، غالبًا ما تستخدم وظيفة Debounce من ReactiveX.


على سبيل المثال:


  1. يتم إدخال كلمة في شريط البحث ويظهر تلميح فقط عندما تكون الفجوة بين مجموعة الحروف أكثر من ثانيتين
  2. عند وضع الإعجابات ، يمكنك النقر فوق 10 مرات في الثانية - ستحدث الكتابة إلى قاعدة البيانات إذا كانت الفجوة في النقرات تزيد عن 2-3 ثواني
  3. ... الخ

المهمة: لجعل الرقم يتغير فقط إذا انقضت أكثر من ثانيتين بين مكابس + أو -. للقيام بذلك ، قم بتحرير فئة BLoC فقط ، يجب أن تظل بقية الكود كما هي.




هذا كل شيء. إذا كان هناك شيء ما ملتوية أو خاطئة ، صحح هنا أو على جيثب ، حاول تحقيق المثل الأعلى :)


الترميز الجيد للجميع!

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


All Articles