متن خبر

نحوه استفاده و ایجاد جریان از ابتدا در دارت و فلاتر – راهنمای مبتدیان

نحوه استفاده و ایجاد جریان از ابتدا در دارت و فلاتر – راهنمای مبتدیان

شناسهٔ خبر: 457974 -




برنامه نویسی می تواند یک ترن هوایی باشد. این شما را از احساس نابغه به احساس کاملاً نادان و دوباره برمی گرداند - همه در یک چشم به هم زدن.

وحشی‌تر از آن این است که این چرخه بارها در طول روز تکرار می‌شود، و در کل دوران حرفه‌ای شما به عنوان یک توسعه‌دهنده نرم‌افزار.

جدا از تجربه شخصی من، نمونه اصلی این پدیده که به ذهن می رسد، مورد این کاربر Reddit است که چند ماه پیش مبارزه خود را به اشتراک گذاشت:

یک کاربر Reddit ناراحتی خود <a href= را با جریان‌های Flutter، کنترل‌کننده‌های جریان، و سوکت‌های وب و نحوه کار آنها به اشتراک می‌گذارد." width="656" height="180" loading="lazy">
یک کاربر Reddit ناراحتی خود را با جریان‌های Flutter، کنترل‌کننده‌های جریان، و سوکت‌های وب و نحوه کار آنها به اشتراک می‌گذارد.

جریان ها یکی از آن مفاهیمی هستند که می توانند شما را از " وای، من خیلی باهوشم 🙈 " به " من خیلی خنگ هستم 💀 احتمالاً باید در مزرعه باشم " را وادار کنند. بسیاری از توسعه دهندگان درک و درک آنها را دشوار می دانند، به ویژه توسعه دهندگان Dart و Flutter جدید.

اگرچه استریم ها ممکن است پیچیده باشند، اما آنقدر پیچیده نیستند که یادگیری آنها غیرممکن باشد. اگر فداکاری و تمرین کافی داشته باشید، می توانید آنها را درک کنید، مهارتی که ممکن است دیر یا زود ضروری شود.

این به این دلیل است که استریم‌ها بنیادی هستند و بسیاری از کتابخانه‌های دارت و SDK مبتنی بر فلاتر (مانند Firebase، سنسورهای دستگاه، برخی تکنیک‌های مدیریت وضعیت، و حتی ایزوله‌های دارت) به شدت به آن‌ها متکی هستند. در نتیجه، یادگیری نحوه استفاده موثر از استریم ها بدون شک مهارت های توسعه شما را افزایش می دهد.

آنچه شما یاد خواهید گرفت

وقتی مطالعه را تمام کردید، باید بتوانید:

درک کنید که Stream ها چه هستند و چه چیزهایی نیستند، سناریوهای بهینه برای استفاده از آنها در برنامه های Dart و Flutter خود را بشناسید، و موقعیت هایی را شناسایی کنید که رویکردهای جایگزین ممکن است مناسب تر باشند.

جریان‌های سفارشی خاص را در Dart ایجاد کنید و از تکنیک‌های پیشرفته برای تغییر آن‌ها بر اساس نیازهای برنامه خود و برای بهبود عملکرد استفاده کنید.

استراتژی‌هایی را برای مقابله با چالش‌های عملکرد رایج مرتبط با جریان‌ها پیاده‌سازی کنید، اطمینان حاصل کنید که برنامه‌های کاربردی شما تحت شرایط مختلف به خوبی اجرا می‌شوند، و بسیاری موارد دیگر.

پیش نیاز: چه چیزی باید بدانید؟

قبل از شروع و برای درک آسان، شما باید درک اولیه ای از موضوعات و مفاهیم زیر داشته باشید:

    کد ناهمزمان : با اصول برنامه نویسی ناهمزمان آشنا شوید، تفاوت آنها با برنامه های همزمان. درک کنید که چگونه تکنیک‌های ناهمزمان به عملکرد، مفاهیم کلیدی مانند آینده، همگام‌سازی/انتظار، تماس‌های برگشتی، و حلقه‌های رویداد برای یک زبان تک رشته‌ای کمک می‌کنند.

    Dart : مطمئن شوید که دانش کاربردی زبان برنامه نویسی دارت، از جمله نحو، انواع داده ها، متغیرها، توابع، کلاس ها و درک اولیه از مدیریت استثناها را دارید.

    Flutter Framework : اگرچه کاملاً ضروری نیست، داشتن درک اولیه از Flutter و اجزای اصلی آن می تواند مفید باشد. با ویجت‌های Flutter، تکنیک‌های مدیریت حالت، ناوبری و چرخه عمر ویجت آشنا شوید تا جریان‌ها را بهتر در برنامه‌های Flutter خود ادغام کنید.

فهرست مطالب

در حالی که من شما را تشویق می‌کنم که هر یک از بخش‌ها را به ترتیبی که نوشته شده‌اند بخوانید، اما اگر بخش قبل از آن را درک می‌کنید، با خیال راحت به هر قسمتی که به آن علاقه دارید بروید.

استریم در دارت چیست؟

کاربردهای واقعی جریان ها

استریم ها چگونه کار می کنند ؟

نحوه کار با استریم ها در دارت

چگونه در دارت استریم خود را ایجاد کنیم

در مورد رسیدگی به خطا با جریان ها چطور ؟

هر چیزی که تغییر می کند نباید یک جریان باشد

نتیجه

چالش سریع

استریم در دارت چیست؟

اگر در حال خواندن این مقاله هستید، به احتمال زیاد عملیات ناهمزمان را درک می کنید - می دانید چگونه از Futures با Async-Await استفاده کنید. و اگرچه ممکن است ایده ای نداشته باشید که آنها چگونه در داخل کار می کنند ( موارد همزمانی حلقه رویداد )، احتمالاً از آنها برای واکشی یک نتیجه JSON از یک API راه دور استفاده کرده اید.

استریم ها شبیه Futures هستند زیرا هر دو به صورت ناهمزمان کار می کنند.

یکی از تفاوت‌های کلیدی این است که وقتی Future فراخوانی می‌شود و شروع می‌شود، می‌تواند مقداری را برگرداند یا خطا می‌کند و سپس متوقف می‌شود. از سوی دیگر، یک Stream می‌تواند یک سری مقادیر (داده‌ها و خطاها) را به طور مداوم ارائه دهد (در ادامه در این مورد بیشتر توضیح خواهیم داد).

پس از نظر فنی درست است که بگوییم Future ها جریان های تک ارزشی یا عملیات جریان پاسخ یک بار مصرف هستند. اگر یک روش یا تابع نیاز به بازگرداندن بیش از یک نتیجه در بازه‌های زمانی مختلف دارد، یا نیاز به به‌روزرسانی‌های مداوم دارد تا به روشی مشابه مدیریت شوند، احتمالاً باید به جریان‌ها نگاه کنید.

کاربردهای واقعی جریان ها

فراتر از برنامه‌های کاربردی ظاهری مانند بازیابی داده‌ها از Firestore یا مدیریت پیام‌های Firebase در یک برنامه چت، سناریوهایی مانند اسکن دستگاه‌های بلوتوث موجود یا جستجوی نقاط اتصال WiFi را در نظر بگیرید.

یک اسکرین شات <a href= که روند جستجوی وای فای را برای نشان دادن یک برنامه واقعی از جریان ها نشان می دهد." width="335" height="744" loading="lazy">

در چنین مواردی، هنگامی که داده ها در دسترس می شوند (اتصال یا دستگاه جدید نقطه دسترسی)، یک رویداد از طریق یک جریان منتشر می شود. سپس شنوندگان مشترک در جریان، این رویدادها را به صورت ناهمزمان دریافت و پردازش می کنند.

این شبیه به پخش آهنگ در Spotify و تماشای ویدیو در سیستم عامل هایی مانند YouTube و Netflix است. سرور موسیقی YouTube یا Spotify آهنگ ها یا ویدیوها را هوشمندانه به قطعات کوچک و قابل مدیریت تقسیم می کند - جریانی از بایت ها، پس لازم نیست تا پایان دانلود برنامه منتظر بمانید. از این رو، نام: جریان.

تصور کنید قبل از پخش یک آهنگ منتظر دانلود باشید!؟!

درخواست HTTP Get شما از جریان‌ها به صورت داخلی استفاده می‌کند

دارت فقط صبر می کند تا جریان به پایان برسد و سپس تمام داده ها را یکباره در قالب یک آینده تکمیل شده برمی گرداند.

 //preceeding code removed for brevity client.getUrl(uri) .then((req) => req.close()) .then((response) => response.transform(utf8.decoder).join()) .then((value) => jsonDecode(value) as List<dynamic>) .then((json) => json.map((map) => Todo.fromJson(map)).toList()) .then((retrievedTodos) { for (final todo in retrievedTodos) { print('Todo: ${todo.title}, Completed: ${todo.completed}'); } }) .catchError((e) { print('Error: $e'); }) .whenComplete(() { client.close(); }); // section only for illustration

در اینجا یک نسخه Async-Await است که نظر داده شده است:

 import 'dart:convert'; import 'dart:io'; class Todo { final int id; final String title; final bool completed; Todo({ required this.id, required this.title, required this.completed, }); factory Todo.fromJson(Map<String, dynamic> json) { return Todo( id: json['id'], title: json['title'], completed: json['completed'], ); } } void main() async { final uri = Uri.parse('https://jsonplaceholder.typicode.com/todos'); final client = HttpClient(); try { final request = await client.getUrl(uri); final response = await request.close(); final jsonString = await response.transform(utf8.decoder).join(); final json = jsonDecode(jsonString) as List<dynamic>; final retrievedTodos = json.map((map) => Todo.fromJson(map)).toList(); for (final todo in retrievedTodos) { print('Todo: ${todo.title}, Completed: ${todo.completed}'); } } catch (e) { print('Error: $e'); } finally { client.close(); } }

جای تعجب نیست که دانلود یک فایل از این تکنیک نیز استفاده می کند.

طبق گفته Remi Rouselet، نویسنده ارائه دهنده و بسته Riverpod، به طور کلی، هنگام برخورد با هر چیزی که شامل نوعی "اتصال" باشد، به جریان نیاز خواهید داشت.

اگر واضح است، بیایید عملی شویم.

جریان ها چگونه کار می کنند؟

جریان ها مشابه تسمه های نقاله ای هستند که معمولاً در فرودگاه ها دیده می شوند.

آنها به عنوان کانال هایی عمل می کنند که به راحتی اقلام مختلف را از یک سر به سر دیگر منتقل می کنند. به طور کلی، چمدان ها یا کیسه ها را به یک تسمه نقاله اضافه می کنید و در مسیر آن حمل می شوند. به همین ترتیب می توانید داده ها یا رویدادها را به یک جریان اضافه کنید.

جایی که شما در اقلام قرار می دهید منبع نامیده می شود.

همانطور که اقلام در امتداد تسمه نقاله حرکت می کنند، کارگران مستقر در امتداد تسمه آنها را مشاهده و با آنها تعامل می کنند. این کارگران نماینده شنوندگان یا مشترکین جریان هستند.

آنها ممکن است موارد را بر اساس معیارهای خاص بررسی، دسته بندی یا دستکاری کنند.

برخی از کارگران ممکن است فقط به انواع خاصی از اقلام علاقه مند باشند و به دیگران اجازه عبور دهند، در حالی که برخی دیگر ممکن است هنگام عبور از ایستگاه خود، موارد را تغییر دهند یا ترکیب کنند. این منعکس کننده مفهوم فیلتر کردن، تبدیل یا تجمع رویدادها در جریان است که در ادامه این قطعه به آن خواهیم پرداخت.

نحوه کار با استریم ها در دارت

شما دو انتخاب دارید:

از جریانی استفاده کنید که از قبل وجود دارد، یا

یکی را از ابتدا بسازید

معمولاً استفاده از جریانی که از قبل وجود دارد، به جای ایجاد جریانی جدید برای استفاده از آن در جای دیگری در برنامه، آسان‌تر است. پس بیایید با ایده استفاده از یک جریان داده‌ای که قبلاً ایجاد شده است شروع کنیم.

اما ابتدا باید بدانید که دو نوع جریان وجود دارد:

    یک جریان تک اشتراکی

    یک جریان پخش

یک جریان اشتراک واحد پیش فرض در Dart است

یک اشتراک تنها به یک شنونده/مشترک در تمام طول عمر خود اجازه می دهد. حتی مهم نیست اشتراک قدیمی را لغو کنید - نمی توانید دوباره مشترک شوید. هر گونه تلاش برای اشتراک مجدد خطای Bad State را به همراه خواهد داشت:

 import 'dart:async'; void main() { // Create a StreamController StreamController<int> streamController = StreamController<int>(); // Listen to the stream StreamSubscription<int> subscription = streamController.stream.listen( (int data) { print('Received data: $data'); }, ); // Cancel the subscription subscription.cancel(); // Try to listen to the stream again with the same subscription try { subscription = streamController.stream.listen( (int data) { print('Received data again: $data'); }, ); } catch (e) { print('Error: $e'); // Handle the error } // Close the stream controller streamController.close(); }}
پیام خطایی <a href= که از جریان دریافت می کنید این است: "خطا: وضعیت بد: جریان قبلاً به آن گوش داده شده است"." width="694" height="55" loading="lazy">

این زمانی مفید است که ترتیب دریافت اطلاعات حیاتی است و هرگونه ناهماهنگی باعث می‌شود که داده‌ها غیرقابل خواندن یا تفسیر آنها غیرممکن باشد، مانند زمانی که درخواست HTTP GET، خواندن یک فایل، یا پردازش پیام‌ها در یک برنامه چت.

همچنین، جریان تک اشتراکی کارآمدترین نوع جریان است، زیرا تا زمانی که شنونده نداشته باشد، شروع به تولید رویداد نمی‌کند، و با لغو اشتراک شنونده، ارسال رویدادها را متوقف می‌کند، حتی اگر هنوز رویدادهای بیشتری برای انتشار وجود داشته باشد.

اما اگر بیشتر از یک شنونده بخواهید چه؟

اگر بخواهید جریان داده یکسانی را در چندین مؤلفه یا ویجت در برنامه خود به اشتراک بگذارید، چه؟ اگر یک ویژگی مشترک شامل به‌روزرسانی‌های بی‌درنگ باشد و بخش‌های مختلف برنامه شما نیاز به واکنش همزمان داشته باشند، چه می‌کنید؟

اینجاست که Broadcast Stream وارد می شود

برخلاف جریان تک اشتراکی، پخش جریانی به هر تعداد شنونده اجازه می دهد. جالب این است که رویدادهای خود را زمانی که آماده هستند بدون تحلیل اینکه آیا شنونده وجود دارد یا خیر، پخش می کند.

این کاملا ناکارآمد است، اینطور نیست؟ پس لازم است در مورد پخش جریانی احتیاط کنید زیرا اگر به درستی مدیریت نشود منجر به نشت حافظه می شود. آخر مگر آنها نمی گویند که با قدرت زیاد مسئولیت بزرگی به همراه دارد؟

پخش جریانی برای موقعیت‌هایی مناسب است که هر رویداد را می‌توان بدون اتکا به رویدادهای قبلی مدیریت کرد و می‌تواند به محض دریافت توسط کاربر پردازش شود - برای مثال، اخبار فوری، امتیازات ورزشی، یا هشدارهای آب‌وهوا.

Livescore لیگ برتر انگلیس برای نشان دادن جریان اعلان‌هایی <a href= که به رویدادهای قبلی متکی نیست استفاده می‌شود" width="507" height="502" loading="lazy">

شایان ذکر است که پس از فعال شدن یک رویداد done ، اشتراک همه مشترکین لغو می شود. سپس هر مشترک جدید به سادگی رویداد انجام شده را دریافت می کند و دیگر گوش نمی دهد.

 import 'dart:async'; void main() { // Create a StreamController StreamController<int> streamController = StreamController<int>(); // Listen to the stream streamController.stream.listen( (int data) { print('Received data: $data'); }, onDone: () { print('Stream is done.'); }, ); // Add data to the stream streamController.add(1); streamController.add(2); // Close the stream controller streamController.close(); // Try to subscribe again after the stream is closed Future.delayed(Duration.zero, () { try { streamController.stream.listen( (int data) { print('New subscriber received data: $data'); }, onDone: () { print('New subscriber received the done event.'); }, ); } catch (e) { print(e); print('New subscriber is no longer listening.'); } }); }
نتیجه پس <a href= از اجرای کد بالا. ابتدا 1 و 2 چاپ می شوند، سپس Stream علامت گذاری شده است. اما اگر دوباره سعی کنید به آن گوش دهید، نشان می دهد که Stream قبلاً به آن گوش داده است" width="448" height="194" loading="lazy">

در مورد ایجاد جریان‌های خودمان که دیگران بتوانند در آن مشترک شوند، چطور؟

چگونه در دارت استریم خود را ایجاد کنیم

در حال حاضر، سه راه برای ایجاد یک جریان جدید در دارت وجود دارد:

    تبدیل جریان های موجود

    با استفاده از یک ژنراتور همگام

    استفاده از کنترل کننده های جریان

نحوه ایجاد جریان با تبدیل جریان های موجود

من واقعاً به اینها به عنوان یک روش مستقل برای ایجاد جریان فکر نمی کردم زیرا شما را ملزم به تکیه بر جریان دیگری می کند. اما تحلیل مستندات Dart باعث شد متوجه شوم که ما در واقع هر زمان که جریان دیگری را تغییر می دهیم یک موجودیت جریان جدیدی ایجاد می کنیم. این کاملا متا است، در واقع ...

 import 'dart:async'; void main() { // Create a stream of integers final Stream<int> originalStream = Stream<int>.fromIterable([1, 2, 3, 4, 5]); // Transform the original stream using map() final Stream<int> transformedStream = originalStream.map((int value) { return value * 2; // Double each integer }); // Listen to the transformed stream final StreamSubscription<int> subscription = transformedStream.listen((int value) { print('Transformed value: $value'); }); // Close the subscription and the streams after a delay Future.delayed(Duration(seconds: 1), () { subscription.cancel(); }); }

نتیجه:

نتیجه کد بالا <a href= که در آن هر داده جریان در دو ضرب می شود: مقدار تبدیل شده: 2، 4، 6 و غیره" width="230" height="176" loading="lazy">
نتیجه کد بالا که در آن هر داده جریان در دو ضرب می شود

این مثال یک جریان داده Firebase Firestore دریافت می‌کند و آن را به رابط کاربری نقشه‌برداری می‌کند:

 //code removed for brevity class FirestoreService { final CollectionReference _collectionReference = FirebaseFirestore.instance.collection('messages'); Stream<List<Map<String, dynamic>>> getMessages() { return _collectionReference.snapshots().map((snapshot) => snapshot.docs.map((doc) => doc.data() as Map<String, dynamic>).toList()); } Future<void> addMessage(String message, String sender) async { // your code... } // other methods removed for brevity } void main() { final firestoreService = FirestoreService(); // Listen to messages final Stream<List<Map<String, dynamic>>> messageStream = firestoreService.getMessages(); messageStream.listen((messages) { print('Received messages: $messages'); }); // Add a new message firestoreService.addMessage('Hello Firestore!', 'Dart User'); } // other code for brevity

سایر روش های تبدیل متداول عبارتند از take() ، expand() و where() . اگر برنامه شما نیاز به تبدیل های پیشرفته تری دارد (به عنوان مثال، تبدیل پاسخ HTTP Get با استفاده از رمزگشایی UTF-8) فراتر از این روش های استاندارد، کلاس ترانسفورماتور جریانی را برای قابلیت های اضافی تحلیل کنید.

نحوه ایجاد جریان با استفاده از ژنراتورها

در اینجا، شما از چیزی که به نام تابع مولد async نامیده می شود استفاده می کنید.

این تابعی است که با async * به جای async مشخص شده است تا آن را از آینده متمایز کند. این تابع به صورت ناهمزمان اجرا می‌شود و هر زمان که یک کلمه کلیدی yield می‌بیند، یک مقدار را برمی‌گرداند، اما مانند یک return ، اجرای کد تابع را متوقف نمی‌کند.

 import 'dart:async'; // Define an asynchronous generator function Stream<int> countStream(int max) async* { for (int i = 1; i <= max; i++) { // Yield each value asynchronously await Future.delayed(Duration(seconds: 1)); yield i; } } void main() { // Create a stream using the asynchronous generator function Stream<int> stream = countStream(6); // Subscribe to the stream stream.listen((value) { print('Received: $value'); }, onDone: () { print('Stream is done'); }); }

در اینجا نحوه کار آن آمده است:

هنگام فراخوانی یا فراخوانی تابع، جریان ایجاد می شود.

اما فقط زمانی شروع به اجرا می‌کند که به جریان گوش می‌دهید، زیرا جریان‌ها بارگذاری تنبلی دارند. می تواند با استفاده از عبارات yield یا yield* رویدادها را در جریان منتشر کند تا زمانی که تابع برگردد و سپس جریان بسته شود.

بازده چه تفاوتی با بازده دارد و چگونه کار می کند؟

در Dart، "return" برای خروج فوری از یک تابع و برگرداندن یک مقدار به تماس گیرنده استفاده می شود. هنگامی که یک تابع با یک دستور بازگشت مواجه می شود، اجرای خود را خاتمه می دهد و کنترل را به همراه مقدار بازگشتی مشخص شده به تماس گیرنده منتقل می کند.

فراخوانی های بعدی تابع از ابتدا اجرا را آغاز می کند.

 import 'dart:async'; // Function that returns a Future<int> after a delay Future<int> fetchUserData() async { await Future.delayed(Duration(seconds: 2)); // Simulate a delay return 42; // Simulated user data } void main() async { print('Fetching user data...'); try { // Initiating the asynchronous operation and waiting for the result int userData = await fetchUserData(); print('User data received: $userData'); } catch (error) { print('Error fetching user data: $error'); } print('Continuing with other tasks...'); }

در مقابل، هنگامی که یک تابع در یک مولد غیر همگام با عبارت "بازده" مواجه می شود، اجرای آن را به حالت تعلیق در می آورد، مقدار مشخص شده توسط "بازده" را به فراخوان دهنده برمی گرداند و وضعیت تابع را حفظ می کند. این به تابع اجازه می دهد تا زمانی که مقدار بعدی درخواست می شود، اجرا را از جایی که متوقف شده است، از سر بگیرد.

 import 'dart:async'; // Asynchronous generator function Stream<int> countStream(int max) async* { for (int i = 1; i <= max; i++) { // Simulate asynchronous delay await Future.delayed(Duration(seconds: 1)); yield i; // Yield each value in the sequence } } void main() async { // Create a stream using the asynchronous generator function Stream<int> stream = countStream(5); // Subscribe to the stream using await for loop await for (int value in stream) { print('Received: $value'); // Simulate additional processing await Future.delayed(Duration(milliseconds: 500)); } print('Stream is done'); }

در مثال بالا، تابع countStream() یک تابع مولد است که با استفاده از کلمه کلیدی "بازده" دنباله ای از اعداد صحیح از 0 تا 4 را با تنبلی تولید می کند.

هر بار که تابع فراخوانی می شود، مقدار بعدی را در دنباله برمی گرداند بدون اینکه کل دنباله را از قبل تولید کند. این می تواند برای حفظ حافظه و پردازش کارآمد مجموعه داده های بزرگ مفید باشد.

توجه داشته باشید که Stream ایجاد شده توسط یک تابع async* همیشه یک جریان تک اشتراکی است. این به این دلیل است که یک تابع async* تا زمانی که مشابه جریان کنترل عادی یک تابع منفرد (ناهمزمان) انجام نشود، به طور عادی اجرا می شود.

اگر می خواهید پخش جریانی داشته باشید چه می کنید - چه کار می کنید؟

نکته: من قبلاً در این مقاله به آن پاسخ داده ام.

در نهایت، نکته جالبی که اسناد رسمی به آن اشاره کرده و من واقعاً به آن توجه نکرده ام این است که:

به ندرت وجود دارد که یک تابع async* یک جریان از هیچ بسازد. باید داده های خود را از جایی دریافت کند و اغلب آن جایی جریان دیگری است.

این باعث می شود همچنان به جریان های دیگر وابسته باشید. اگر بخواهید دانه بندی کنید و از صفر شروع کنید چه؟ اینجاست که کلاس StreamController وارد می شود.

نحوه ایجاد جریان با استفاده از کنترل کننده های جریان

کنترل‌کننده‌های جریان برای موقعیت‌هایی مناسب هستند که رویدادهای جریان شما از بخش‌های مختلف برنامه شما می‌آیند و/یا نمی‌توان آنها را از جریان یا آینده دیگری دریافت کرد.

چند مثالی که به ذهن می رسد مدیریت رویدادهای ورودی کاربر، مدیریت داده ها از منابع مختلف، یا ایجاد رویدادهای سفارشی در برنامه شما مانند به روز رسانی وضعیت، اعلان های پیشرفت یا هشدارهای سیستم است.

همانطور که متوجه شدم کنترل کننده های جریان سطح پایینی دارند. آنها فقط به شما یک جریان نمی دهند، بلکه راه هایی را به شما می دهند تا در هر نقطه ای رویدادها را به آن اضافه کنید، از جمله منطق لازم برای مدیریت شنوندگان و مکث کردن .

 import 'dart:async'; void main() { // Create a stream controller to manage user input events StreamController<String> userInputController = StreamController<String>(); // Listen to user input events userInputController.stream.listen((String userInput) { print('User input: $userInput'); }); // Simulate user input events userInputController.add('Hello'); userInputController.add('World'); // Create a custom stream controller for progress notifications StreamController<double> progressController = StreamController<double>(); // Listen to progress notifications progressController.stream.listen((double progress) { print('Progress: $progress'); }); // Simulate progress notifications for (double i = 0; i <= 1; i += 0.2) { progressController.add(i); } // Create a custom stream controller for system alerts StreamController<String> systemAlertController = StreamController<String>(); // Listen to system alerts systemAlertController.stream.listen((String alert) { print('System Alert: $alert'); }); // Simulate system alerts systemAlertController.add('System overload detected!'); systemAlertController.add('Database connection lost!'); // Close all stream controllers when done userInputController.close(); progressController.close(); systemAlertController.close(); }

دارای چهار روش برگشت تماس است:

    onListen

    onCancel

    onResume

    onPause

اگر می‌خواهید بدانید چه زمانی جریان مشترک شده است، هنگام ایجاد StreamController ، یک onListenHandler را به پارامتر onListen ارسال کنید.

 import 'dart:async'; void main() { // Create a StreamController with an onListen callback StreamController<int> streamController = StreamController<int>( onListen: () { print('Stream has been subscribed to.'); }, ); // Listen to the stream streamController.stream.listen((int data) { print('Received data: $data'); }); // Add data to the stream streamController.add(1); streamController.add(2); streamController.add(3); // Close the stream controller when done streamController.close(); }

پاسخ تماس onListen زمانی فراخوانی می‌شود که جریان اولین مشترک خود را دریافت کند. از طرف دیگر، onCancel زمانی فعال می شود که کنترلر آخرین مشترک خود را از دست بدهد.

 import 'dart:async'; void main() { // Create a StreamController with onCancel callback StreamController<int> streamController = StreamController<int>( onCancel: () { print('Last subscriber canceled, stream controller is now inactive.'); }, ); // Listen to the stream StreamSubscription<int> subscription = streamController.stream.listen((int data) { print('Received data: $data'); }); // Add data to the stream streamController.add(1); streamController.add(2); // Cancel the subscription subscription.cancel(); // Add more data to the stream after canceling the subscription streamController.add(3); // Close the stream controller streamController.close(); } //##Note: //When you use async* and yield*, //you're creating a function that can asynchronously yield values, //potentially generating a new stream of values each time it's called. //When you return a stream, //you're passing around a reference to an existing stream object without //necessarily generating new values or modifying the stream itself.

یادتان هست که گفتم StreamController ها سطح پایینی دارند؟

این به طور کلی به این معنی است که شما بر همه چیز کنترل دارید. با این حال، این مسئولیت با اجرای آپشن های ی است که روش‌های ایجاد جریان در سطح بالاتر خارج از جعبه ارائه می‌کنند.

یکی از این ویژگی ها به عنوان "احترام به مکث" شناخته می شود.

هنگامی که یک اشتراک جریانی در یک تولیدکننده async* متوقف می‌شود، تابع مولد به طور خودکار در یک بیانیه بازده متوقف می‌شود و اطمینان حاصل می‌کند که هیچ رویداد جدیدی منتشر نمی‌شود تا زمانی که اشتراک از سر گرفته شود.

اما با StreamControllers، رویدادها همچنان در طول مکث ایجاد و بافر می شوند. اگر کد تولید کننده رویداد نتواند به مکث احترام بگذارد، اندازه بافر می تواند به طور نامحدود رشد کند و منجر به مشکلات بالقوه حافظه شود.

علاوه بر این، اگر شنونده مدت کوتاهی پس از مکث، گوش دادن را متوقف کند، تمام تلاشی که برای ایجاد بافر صرف می‌شود به هدر می‌رود. به شدت ناکارآمد است، اینطور نیست؟ یک عملیات طولانی مدت را تصور کنید.

در اینجا نحوه حل آن آمده است:

 Stream<int> integerCounter(Duration interval, [int? maxCount]) { late StreamController<int> controller; void onListenHandler() { //code removed for brevity; } void onPauseHandler() { //code removed for brevity; } void onResumeHandler() { //code removed for brevity; } void onCancelHandler() { //code removed for brevity; } controller = StreamController<int>( onListen: onListenHandler, onPause: onPauseHandler, onResume: onResumeHandler, onCancel: onCancelHandler, ); return controller.stream; }

یکی دیگر چیزی است که من آن را "همگام سازی مکث-اشتراک" می نامم

این اصطلاح به همگام سازی بین حالت های اشتراک و مکث یک StreamController اشاره دارد. اگر هر دو حالت اشتراک و مکث به طور همزمان تغییر کنند، فقط پاسخ تماس onListen یا onCancel فعال می شود.

به همین دلیل است که توصیه می‌شود برای کاهش مشکلات احتمالی و اطمینان از عملکرد مناسب، همه شنوندگان موجود - onListen ، onCancel ، onPause و onResume را پیاده‌سازی کنید. به این ترتیب، می‌توانید به‌طور مؤثری تغییرات در حالت مکث را کنترل کنید و از ردیابی اشکال‌هایی که ممکن است از رفتار غیرمنتظره ناشی می‌شوند اجتناب کنید.

آه، و هرگز فراموش نکنید که کنترلر خود را دور بریزید:

 import 'dart:async'; void main() { // Create a StreamController StreamController<int> streamController = StreamController<int>(); // Listen to the stream StreamSubscription<int> subscription = streamController.stream.listen((int data) { print('Received data: $data'); }); // Add data to the stream streamController.add(1); streamController.add(2); // Dispose of the subscription and stream controller subscription.cancel(); streamController.close(); // call the close method to dispose }

در مورد رسیدگی به خطا در جریان ها چطور؟

هنگامی که خطاها در یک جریان رخ می دهد، جریان آنها را به طور مشابه با نحوه مدیریت رویدادهای داده مدیریت می کند - با آگاه کردن شنوندگان از طریق رویدادهای خطا. به طور کلی، جریان ها دو رفتار واضح را در واکنش به خطا نشان می دهند:

    جریان اولین رویداد خطا را مطلع می کند و سپس پردازش بیشتر را متوقف می کند.

    جریان رویداد(های) خطا را مطلع می کند اما به ارائه رویدادهای بعدی ادامه می دهد.

بیایید هر کدام را در یک زمان مصرف کنیم.

توقف بعد از اولین خطا

در این سناریو، جریان پس از مواجهه با اولین خطا متوقف می‌شود، اما بینشی در مورد مشکل اولیه ارائه می‌کند و هرگونه انتقال رویداد بعدی را متوقف می‌کند. این زمانی مفید است که ترتیب اهمیت حیاتی است و هر قطعه گم شده برای غیرقابل استفاده کردن کل فایل کافی است.

 import 'dart:async'; void main() { // Create a StreamController StreamController<int> streamController = StreamController<int>(); // Listen to the stream StreamSubscription<int> subscription = streamController.stream.listen( (int data) { print('Received data: $data'); }, onError: (error) { print('Error occurred: $error'); }, onDone: () { print('Stream is done.'); }, ); // Add data to the stream streamController.add(1); streamController.add(2); streamController.addError('Error: Something went wrong'); // Simulate an error streamController.add(3); // Close the stream controller streamController.close(); }

با توجه به آنچه آموخته اید، این برای عملکردهای مولد ناهمگام یا جریان های اشتراک تکی عجیب است. اگر مورد شما متفاوت باشد، مثلاً می‌خواهید بعد از اینکه در جریان با خطا مواجه شدید، ادامه دهید، چه می‌کنید؟

ادامه بعد از اولین خطا

برخلاف سناریوهایی که جریان‌ها پس از اولین خطا متوقف می‌شوند، ادامه دادن پس از خطاها جریان را قادر می‌سازد تا جریان خود را حفظ کند. این بینش ها و به روز رسانی های مداوم را برای مصرف کنندگان پایین دستی فراهم می کند.

این رویکرد در سناریوهایی که عملکرد بدون وقفه جریان از اهمیت بالایی برخوردار است، مانند پردازش بی‌درنگ داده یا سیستم‌های نظارت مستمر، بسیار ارزشمند است. جریان‌هایی که پس از خطاها ادامه می‌یابند، انعطاف‌پذیری و سازگاری را ارائه می‌کنند و تضمین می‌کنند که اطلاعات حیاتی به دلیل حوادث جداگانه از بین نمی‌روند.

بیایید به یک مثال نگاه کنیم:

 import 'dart:async'; void main() async { // Create a stream controller StreamController<int> streamController = StreamController<int>(); // Generate numbers asynchronously with a delay of 1 second int count = 0; Timer.periodic(Duration(seconds: 1), (Timer timer) { // Simulate errors for demonstration if (count % 3 == 0) { streamController.addError('Error: Failed to generate number $count'); } else { streamController.add(count); } count++; }); // Listen to the stream streamController.stream.listen( (int data) { print('Received data: $data'); }, onError: (error) { print('Error occurred: $error'); }, ); }

هر چیزی که تغییر می کند نباید یک جریان باشد

جریان‌ها با انتشار رویدادها (داده‌ها یا مقادیر خطا) بدون توجه به نحوه مصرف آن‌ها، عملکرد بسیار خوبی ارائه می‌دهند و این به توسعه‌دهندگان انعطاف‌پذیری برای نوشتن کد با جفت کم و توسعه‌پذیری بالا می‌دهد. اما آنها نباید به هر چیزی که تغییر می کند گره بخورند.

به گفته راندال شوارتز، مدیریت ایالتی مثال خوبی برای این امر است .

من با او تماس گرفتم تا مطمئن شوم که موضع او را درک می کنم، و این چیزی است که او می گوید:

"تفاوت اصلی، همانطور که توسط رمی برای من روشن شد، این است که جایی برای استریم ها وجود دارد که هر رویداد باید در آن گنجانده شود، در مقایسه با مدیریت معمولی ایالت، که در آن فقط آخرین وضعیت (و اعلان زمانی که تغییر می کند) مرتبط است. به سرعت از 1 به 2 به 3 می رسد، اما شما سپس بر اساس 3 بازسازی می کنید، کافی است. "

به عبارت دیگر، شما به متوسط ​​اهمیت نمی دهید، فقط آخرین ها.

State چیزی است که باید در طول چرخه عمر برنامه خود مدیریت کنید. اگر این کار ضعیف انجام شود، برنامه‌های شما ممکن است به دلیل بازسازی‌های بیش از حد یا در مقیاس بزرگ دچار مشکلات عملکردی شوند و تاخیر داشته باشند.

پس به‌روزرسانی‌های غیرضروری را به حداقل برسانید و فقط اجزایی را بازسازی کنید که واقعاً برای بهینه‌سازی عملکرد کلی نیاز به بازسازی دارند. به یاد داشته باشید که دارت تک رشته ای است.

فقط شروع کنید

من از شما انتظار ندارم که تمام جزئیات ارائه شده در اینجا را یکباره درک کنید، حتی اگر من ساعت های بی شماری را در طول هفته ها برای اصلاح این آموزش اختصاص داده ام. پس تحت فشار نباشید.

در عوض، با خیال راحت برای زمانی که برای ادامه آماده هستید، نشانک گذاری کنید. اگر چیزی مبهم به نظر می رسد، لطفاً به اعتبارات و منابع توصیه شده مراجعه کنید یا می توانید در توییتر با من تماس بگیرید.

حقیقت غیرقابل انکار این است که می‌توانید آموزش‌ها و ویدیوهای بی‌پایانی را مصرف کنید، اما اعتماد به نفس واقعی زمانی به دست می‌آید که دانش خود را برای مشکلات دنیای واقعی به کار می‌گیرید و آنها را حل می‌کنید (در زیر سه مورد را برای شما دارم)

به این آموزش نزدیک شوید، طوری که از یک جریان برای مدیریت یک منبع بزرگ استفاده می کنید - آن را به قطعات کوچکتر و قابل هضم تقسیم کنید و آنها را به راحتی پردازش کنید. مهم نیست که نامنظم باشد، فقط مطمئن شوید که با آن مقابله کنید.

در صورت بروز هر گونه سردرگمی، آن را در نظرات به اشتراک بگذارید، در توییتر برای من توییت کنید (Now X)، یا از طریق DM با من تماس بگیرید. من خوشحال خواهم شد که به شما کمک کنم تا آنها را حل کنید و کمی شفافیت ارائه دهید. خداحافظ!

چالش سریع

    چگونه سبک تایپ ChatGPT را با استریم ها پیاده سازی می کنید؟

    بگویید به شما وظیفه جدیدی محول شده است. با فشار دادن دکمه، برنامه شما باید:
    – دانلود فایل فشرده
    – فایل را در یک اکسترکت کنید
    - یک فایل باینری اجرایی را پیدا کنید و آن را اجرا کنید
    - فهرستی از دایرکتوری هایی که باید به PATH اضافه شوند را برگردانید.

چگونه آن را با آنچه تاکنون در این آموزش آموخته اید حل می کنید؟

3. چگونه می توانید از استریم ها برای برقراری ارتباط در زمانی که کاربر در حال تایپ کردن است یا نه استفاده کنید؟

وام:

    مبانی جریان فلاتر برای مبتدیان توسط Dane Mackier.

    استریم ها: برنامه نویسی ناهمزمان با دارت اثر پریانکا تیاگی

    تفاوت بین await for و listen پاسخ در StackOverflow.

    راهنمای ساده مبتدیان برای استریم ها | مبانی جریان فلاتر و دارت توسط FilledStacks [ویدیو یوتیوب]

    Streams: مستندات API در Flutter dot Dev

خبرکاو

ارسال نظر




تبليغات ايهنا تبليغات ايهنا

تمامی حقوق مادی و معنوی این سایت متعلق به خبرکاو است و استفاده از مطالب با ذکر منبع بلامانع است