دارت تيارات أساسيات

هذا هو الجزء الثاني من سلسلتي عن Flutter Architecture:



التدفقات هي لبنة البناء الرئيسية في RxVMS ، فهمهم ضروري للغاية للعمل مع هذه المكتبة ، لذلك سنتناولها بمزيد من التفصيل في هذا المنشور .


اتضح أن تضمين Rx في هذا المنشور سيجعله طويلاً ، لذا قسّمته إلى جزأين.


دعها تتدفق


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


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


ما هي المواضيع؟


في رأيي ، أفضل تشبيه للخيوط هو الحزام الناقل. يمكنك وضع شيء ما على أحد نهايته وسيتم نقل "شيء" تلقائيًا إلى الطرف الآخر. على عكس خط الأنابيب الفعلي ، تقوم مؤشرات الترابط بمعالجة كائنات البيانات ، ونقلها تلقائيًا من البداية - لكن أين؟ كما هو الحال في خط الأنابيب الحقيقي ، إذا لم يكن هناك شيء لالتقاط البيانات على الطرف الآخر ، فسوف "يسقط" ويختفي ببساطة (هذا ، بالطبع ، ليس صحيحًا تمامًا بالنسبة إلى دارت ستريمز ، لكن من الأفضل التعامل مع التدفقات كما لو كان الأمر كذلك) .



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



تذكر:


  • إذا لم يتم تعيين الملاءمة ، ستختفي البيانات ببساطة إلى الأبد ولن تكون هناك طريقة لاستعادتها مرة أخرى (مرة أخرى ، ليس بالضبط مع Dart Streams ، ولكن من الأفضل أن تتظاهر بأنها)
  • بعد إرسال البيانات إلى الدفق ، لا تحتاج إلى إيقاف البرنامج مؤقتًا والانتظار حتى يصل إلى نهايته ، كل هذا يحدث في الخلفية.
  • يمكن للمصيدة استقبال البيانات في أي وقت ، فليس من الضروري على الفور بعد إرسالها (ولكن لا داعي للقلق ، فالتيارات سريعة جدًا بالفعل). تخيل أنك لا تعرف مدى سرعة أو مدة حركة حزام النقل. هذا يعني أن وضع شيء ما في الدفق منفصل تمامًا عن رد الفعل على العنصر في الطرف الآخر. سيعمل الفخ الخاص بك ويلتقط العنصر عندما يصل إلى هناك. (قد يدرك البعض منكم أن هذا يتناسب تمامًا مع الطريقة التفاعلية التي يقوم بها Flutter بتحديث عناصر واجهة المستخدم)
  • يمكنك ضبط فخ طويل قبل بدء العمل ويظهر العنصر الأول
  • يعمل التدفق وفقًا لمبدأ FIFO. تأتي البيانات دائمًا بالترتيب الذي وضعت به في التدفق.

ما هو آر إكس؟


آر إكس ، اختصار لامتدادات رد الفعل ، هي تيارات المنشطات. هذا مفهوم شبيه جدًا بـ Streams ، والذي تم اختراعه من قبل فريق Microsoft لإطار عمل .Net. نظرًا لأن .Net يحتوي بالفعل على نوع الدفق ، والذي يستخدم للملف I / O ، فقد أطلقوا على Rx تدفقات دلالات قابلة للملاحظة وأنشأوا العديد من الوظائف لمعالجة البيانات التي تمر عبرها. يحتوي Dart على Streams مضمنًا في مواصفاته اللغوية التي توفر بالفعل معظم هذه الوظائف ، ولكن ليس جميعها. هذا هو السبب في تطوير حزمة RxDart ؛ يعتمد على دارت دفق ، لكنه يمتد من وظائفهم. سأغطي Rx و RxDart في الجزء التالي من هذه السلسلة.


بعض المصطلحات


يستخدم Dart Streams و Rx بعض المصطلحات التي قد تبدو مخيفة ، لذلك هذه هي الترجمة. أولاً يأتي مصطلح دارت ، ثم آر إكس.


  • تيار / يمكن ملاحظتها . هذا هو "خط الأنابيب" الموضح سابقًا. يمكن تحويل الدفق إلى ملاحظته وحيثما يتوقع الدفق ، يمكنك تعيين ملاحظة. لذلك لا تخلط إذا مزجت هذه المصطلحات في عملية التوضيح
  • الاستماع / الاشتراك - تعيين فخ المستمع
  • StreamController / الموضوع . الجانب "الأيسر" من حزام النقل حيث يمكنك وضع البيانات في البث. أنها تختلف قليلاً في خصائصها وخصائصها ، ولكنها تخدم نفس الغرض.
  • انبعاث عنصر / البيانات . اللحظة التي تظهر فيها البيانات عند خروج "خط الأنابيب"

خلق تيار


إذا كنت تنوي متابعة دراسة الموضوع ، فيرجى استنساخ هذا المشروع بأمثلة. سأستخدم نظام اختبار Dart / Flutter.


لإنشاء دفق ، يمكنك إنشاء StreamController


var controller = new StreamController<String>(); controller.add("Item1"); //      

يحدد نوع القالب (في هذه الحالة سلسلة) عند إنشاء StreamController نوع الكائنات التي يمكننا إرسالها إلى الدفق. يمكن أن يكون أي نوع! يمكنك إنشاء StreamController<List<MyObject>>() إذا كنت ترغب في ذلك وسيقوم الدفق بنقل الورقة بأكملها بدلاً من كائن واحد.


وضع الفخ


إذا قمت بإجراء الاختبار المحدد ، فلن تتمكن من رؤية أي شيء ، لأنه لم يحدث شيء في خط الإنتاج. الآن تعيين الفخ:


 var controller = new StreamController<String>(); controller.stream.listen((item) => print(item)); //  controller.add("Item1"); controller.add("Item2"); controller.add("Item3"); 

الآن يتم تعيين اعتراض باستخدام الأسلوب .listen() . يبدو السجل وكأنه controller.stream.listen ، لكن إذا قمت بتمريره للخلف ، مثل نوع من الألبوم من الستينيات ، فسيظهر المعنى الحقيقي لما هو مكتوب: "استمع إلى دفق وحدة التحكم هذه"


تحتاج إلى تمرير دالة معينة إلى الأسلوب .listen() أجل معالجة البيانات الواردة بطريقة أو بأخرى. يجب أن تقبل الدالة معلمة من النوع المحدد عند إنشاء StreamController ، في هذه الحالة ، سلسلة.


إذا قمت بتشغيل الرمز أعلاه ، سترى


 Item1 Item2 Item3 

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


نهاية الاستماع


غاب الرمز أعلاه الجزء الصغير ولكن المهم. listen() بإرجاع StreamSubscription - كائن اشتراك في الدفق. استدعاء إلى طريقة .cancel() إنهاء الاشتراك وتحرير الموارد ومنع استدعاء وظيفة الاستماع الخاصة بك بعد أن يصبح غير ضروري.


 var controller = new StreamController<String>(); StreamSubscription subscription = controller.stream.listen((item) => print(item)); // This is the Trap controller.add("Item1"); controller.add("Item2"); controller.add("Item3"); //    ,        //  ,     Stream   await Future.delayed(Duration(milliseconds: 500)); subscription.cancel; 

تفاصيل المستمع


يمكن أن تكون وظيفة listen() إما لامدا أو وظيفة بسيطة.


 void myPrint(String message) { print(message); } StreamSubscription subscription = controller.stream.listen((item) => print(item)); //  - StreamSubscription subscription2 = controller.stream.listen(myPrint); //    StreamSubscription subscription3 = controller.stream.listen((item) { print(item); print(item.toUpperCase); }); // - 

ملاحظة مهمة: تسمح معظم تدفقات Dart بالاشتراك لمرة واحدة فقط ، أي أنه لا يمكن إعادة الاشتراك بعد اكتمال الاشتراك - وهذا سيؤدي إلى استثناء. هذا هو اختلافهم عن تطبيقات آر إكس الأخرى.


يبدو التوقيع الكامل listen() كما يلي:


  /* excerpt from the API doc * The [onError] callback must be of type `void onError(error)` or * `void onError(error, StackTrace stackTrace)`. If [onError] accepts * two arguments it is called with the error object and the stack trace * (which could be `null` if the stream itself received an error without * stack trace). * Otherwise it is called with just the error object. * If [onError] is omitted, any errors on the stream are considered unhandled, * and will be passed to the current [Zone]'s error handler. * By default unhandled async errors are treated * as if they were uncaught top-level errors. * * If this stream closes and sends a done event, the [onDone] handler is * called. If [onDone] is `null`, nothing happens. * * If [cancelOnError] is true, the subscription is automatically canceled * when the first error event is delivered. The default is `false`. */ StreamSubscription<T> listen(void onData(T event), {Function onError, void onDone(), bool cancelOnError}); 

هذا يعني أنه يمكنك القيام بأكثر من مجرد تمرير معالج واحد للبيانات المرسلة. يمكنك أيضًا الحصول على معالج للأخطاء ، وآخر لإغلاق الدفق على جانب وحدة التحكم ( onDone ). الاستثناءات التي يتم onError() من داخل Stream onError() إذا قمت onError() ، وإلا سيتم ابتلاعها ببساطة ولن تعرف أبدًا حدوث خطأ ما.


مثال على الرفرفة


لتسهيل فهم الفصول التالية ، قمت بإنشاء فرع مستودع منفصل.
يرجى استنساخ لها


كمثال أول ، أخذت تطبيق العداد المعروف الذي تحصل عليه عند إنشاء مشروع Flutter جديد ، وإعادة تنظيمه قليلاً. أضفت فئة نموذجية لعقد حالة التطبيق ، والتي هي في الأساس قيمة مضادة:


 class Model { int _counter = 0; StreamController _streamController = new StreamController<int>(); Stream<int> get counterUpdates => _streamController.stream; void incrementCounter() { _counter++; _streamController.add(_counter); } } 

هنا يمكنك رؤية قالب نموذجي للغاية: بدلاً من نشر StreamController بالكامل ، نقوم ببساطة بنشر خاصية Stream الخاصة به.


لجعل النموذج متاحًا لواجهة المستخدم ، جعلته حقلًا ثابتًا في كائن التطبيق لأنني لم أرغب في إدخال InheritedWidget أو ServiceLocator. على سبيل المثال البسيط ، هذا سوف يفلت من العقاب ، لكنني لن أفعل ذلك في هذا التطبيق!


أضف إلى main.dart :


 class _MyHomePageState extends State<MyHomePage> { int _counter = 0; StreamSubscription streamSubscription; @override void initState() { streamSubscription = MyApp.model.counterUpdates.listen((newVal) => setState(() { _counter = newVal; })); super.initState(); } //   State   ,   , //       @override void dispose() { streamSubscription?.cancel(); super.dispose(); } 

initState() مكان جيد لتعيين المستمع ، initState() صالحين في لعبة الرشق بالسهام ، initState() دائمًا الاشتراك في dispose() ، أليس كذلك؟


في شجرة عنصر واجهة المستخدم ، نحتاج فقط إلى تكييف معالج onPressed لزر FAB (الزر ذو الحركة العائمة).


 floatingActionButton: new FloatingActionButton( onPressed: MyApp.model.incrementCounter, tooltip: 'Increment', child: new Icon(Icons.add), ), 

بهذه الطريقة ، أنشأنا فاصلًا نظيفًا بين العرض والطراز باستخدام الدفق.


تطبيق StreamBuilder


مصدر


بدلاً من استخدام initState() و setState() لتلبية احتياجاتنا ، يأتي StreamBuilder عنصر واجهة مستخدم StreamBuilder مناسب. كما كنت قد خمنت ، فإنه يأخذ وظيفة الدفق وطريقة البناء التي تسمى كلما عاد الدفق قيمة جديدة. والآن نحن لسنا بحاجة إلى تهيئة واضحة وإصدار:


 body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( 'You have pushed the button this many times:', ), StreamBuilder<int>( initialData: 0, stream: MyApp.model.counterUpdates, builder: (context, snappShot) { String valueAsString = 'NoData'; if (snappShot != null && snappShot.hasData) { valueAsString = snappShot.data.toString(); } return Text( valueAsString, style: Theme.of(context).textTheme.display1, ); }), ], ), ), 

نحن على وشك الانتهاء ، أعدك. فيما يلي ثلاثة أشياء يجب أن تعرفها:


  • تتمثل الميزة الكبيرة لاستخدام StreamBuilder في الحل الأول في أن استدعاء setState() في listen() يعيد ترتيب الصفحة بأكملها دائمًا ، بينما يقوم StreamBuilder باستدعاء منشئها فقط
  • يحتوي المتغير snapShot على أحدث البيانات المستلمة من Stream. تحقق دائمًا من احتوائه على بيانات صالحة قبل استخدامه.
  • استنادًا إلى مبادئ التهيئة أثناء ، لا يمكن أن يحصل StreamBuilder على قيمة أثناء الإطار الأول. للتغلب على هذا ، نقوم بتمرير قيمة initialData ، والتي يتم استخدامها للتجميع الأول ، أي للإطار الأول من الشاشة. إذا لم نجتاز البيانات initialData ، initialData البناء الخاصة بنا للمرة الأولى مع بيانات غير صالحة. بديل لاستخدام initialData هو إرجاع عنصر واجهة مستخدم النائب إذا كانت snapShot صالحة ، والتي يتم عرضها حتى نحصل على بيانات صالحة ، على سبيل المثال:


     // ,           StreamBuilder<int>( stream: MyApp.model.databaseUpdates, builder: (context, snappShot) { if (snappShot != null && snappShot.hasData) { return Text( snappShot.data.toString(), style: Theme.of(context).textTheme.display1, ); } //      ,   Spinner return CircularProgressIndicator (); }) 


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



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


All Articles