مرحباً بكم أيها المطورون! اليوم، سنتعمق في عالم تدفق البيانات (Streams) في Dart، وهو حجر الزاوية لبناء التطبيقات اللحظية والتفاعلية. سنتعلم كيفية إنشاء تدفقات البيانات، إضافة عناصر إليها، والاستماع للتغيرات بكفاءة.
الخطوة 1: فهم Streams و StreamController
في Dart، يمثل Stream سلسلة من الأحداث أو البيانات غير المتزامنة. تخيلها كأنبوب يتدفق عبره الماء (البيانات) بمرور الوقت. لإنشاء والتحكم في هذا التدفق، نستخدم StreamController.
ملاحظة تقنية:
StreamControllerهو أداة قوية تسمح لك بإنشاءStreamيدويًا وإضافة البيانات إليه (عبرsink) والتحكم في دورة حياته.
import 'dart:async'; // نحتاج هذه المكتبة للتعامل مع Streams و StreamController
void main() {
// إنشاء StreamController. هذا الكنترولر سيسمح لنا بإضافة البيانات إلى Stream
// وسيوفر لنا Stream للاستماع إليه.
final StreamController<int> controller = StreamController<int>();
print('تم إنشاء StreamController.');
}
الخطوة 2: إضافة البيانات إلى التدفق والاستماع إليها
بعد إنشاء StreamController، يمكننا الآن إضافة البيانات إلى التدفق باستخدام خاصية sink الخاصة به، ثم الاستماع إلى هذه البيانات باستخدام خاصية stream.
import 'dart:async';
void main() {
final StreamController<int> controller = StreamController<int>();
// الاستماع إلى التدفق (Stream). كلما تم إضافة بيانات، سيتم استدعاء الدالة داخل listen.
controller.stream.listen(
(data) {
print('تم استقبال بيانات: $data');
},
onError: (error) {
print('حدث خطأ: $error'); // معالجة الأخطاء المحتملة
},
onDone: () {
print('اكتمل التدفق بنجاح.'); // يتم استدعاؤها عند إغلاق التدفق
},
cancelOnError: false, // لا توقف الاستماع إذا حدث خطأ
);
// إضافة بيانات إلى التدفق عبر sink
controller.sink.add(10);
controller.sink.add(20);
controller.sink.add(30);
// إغلاق التدفق بعد الانتهاء من إضافة البيانات
// هذا سيؤدي إلى استدعاء onDone في المستمع
controller.close();
print('تم إضافة البيانات وتم إغلاق الكنترولر.');
}
الخطوة 3: تحويلات التدفق (Stream Transformations) ومعالجة الأخطاء
يمكننا تطبيق تحويلات مختلفة على التدفق مثل map لتغيير البيانات، و where لتصفية البيانات، أو take لأخذ عدد محدد من العناصر. كما يمكننا إرسال الأخطاء عبر التدفق للتعامل معها بشكل استباقي.
ملاحظة تقنية: تحويلات التدفق هي أدوات قوية لإنشاء سلاسل معالجة البيانات بكفاءة، مما يجعل الكود أكثر وضوحًا وقابلية للصيانة.
import 'dart:async';
void main() async { // استخدام async لانتظار بعض العمليات
final StreamController<int> controller = StreamController<int>();
// إنشاء Stream جديد عن طريق تحويل Stream الأصلي
// هنا، نقوم بمضاعفة كل رقم وتصفية الأرقام الزوجية فقط
final Stream<int> transformedStream = controller.stream
.map((data) => data * 2) // مضاعفة كل قيمة
.where((data) => data % 2 == 0); // تصفية الأرقام الزوجية فقط
transformedStream.listen(
(data) {
print('بيانات معدلة (زوجية): $data');
},
onError: (error) {
print('خطأ في التدفق المعدل: $error');
},
onDone: () {
print('اكتمل التدفق المعدل.');
},
);
// إضافة بعض البيانات، بما في ذلك رقم فردي وخطأ
controller.sink.add(1); // ستتم مضاعفته إلى 2 ثم تصفية (يبقى)
controller.sink.add(2); // ستتم مضاعفته إلى 4 ثم تصفية (يبقى)
controller.sink.add(3); // ستتم مضاعفته إلى 6 ثم تصفية (يبقى)
controller.sink.add(4); // ستتم مضاعفته إلى 8 ثم تصفية (يبقى)
controller.sink.addError('حدث خطأ مخصص!'); // إرسال خطأ عبر التدفق
await Future.delayed(Duration(milliseconds: 100)); // إعطاء وقت للاستماع لمعالجة البيانات
controller.sink.add(5); // ستتم مضاعفته إلى 10 ثم تصفية (يبقى)
// إغلاق الكنترولر
controller.close();
}
الكود النهائي الكامل
هذا هو الكود الكامل الذي يجمع كل المفاهيم التي تعلمناها، بما في ذلك إنشاء StreamController، إضافة البيانات، الاستماع، التحويل، ومعالجة الأخطاء.
import 'dart:async';
void main() async {
// 1. إنشاء StreamController للتحكم في تدفق البيانات.
final StreamController<int> controller = StreamController<int>();
print('-------------------------------------');
print('بدء الدرس: التعامل مع تدفق البيانات (Streams)');
print('-------------------------------------');
// 2. إنشاء تدفق معدل: يضاعف الأرقام ويصفي الأرقام الزوجية فقط.
final Stream<int> transformedStream = controller.stream
.map((data) => data * 2) // تحويل: مضاعفة كل قيمة
.where((data) => data % 2 == 0); // تصفية: الاحتفاظ بالأرقام الزوجية فقط
// 3. الاستماع إلى التدفق المعدل.
transformedStream.listen(
(data) {
print('✅ تم استقبال بيانات معدلة (زوجية): $data');
},
onError: (error) {
print('❌ خطأ في التدفق المعدل: $error'); // معالجة الأخطاء
},
onDone: () {
print('🎉 اكتمل التدفق المعدل بنجاح.'); // عند إغلاق التدفق
},
cancelOnError: false, // لا توقف الاستماع إذا حدث خطأ
);
// 4. إضافة بيانات إلى التدفق الأصلي عبر sink.
print('\n--- إضافة بيانات إلى Stream ---');
controller.sink.add(1); // (1 * 2 = 2) -> زوجي
controller.sink.add(2); // (2 * 2 = 4) -> زوجي
controller.sink.add(3); // (3 * 2 = 6) -> زوجي
controller.sink.add(4); // (4 * 2 = 8) -> زوجي
// 5. إرسال خطأ عبر التدفق.
print('--- إرسال خطأ ---');
controller.sink.addError('مشكلة في معالجة البيانات!');
// 6. إضافة المزيد من البيانات بعد الخطأ.
controller.sink.add(5); // (5 * 2 = 10) -> زوجي
controller.sink.add(6); // (6 * 2 = 12) -> زوجي
// إعطاء فرصة قصيرة للمستمع لمعالجة جميع الأحداث.
await Future.delayed(Duration(milliseconds: 50));
// 7. إغلاق StreamController. هذا سيؤدي إلى استدعاء onDone.
print('\n--- إغلاق StreamController ---');
controller.close();
print('-------------------------------------');
print('انتهى الدرس.');
print('-------------------------------------');
}
النتيجة المتوقعة
عند تشغيل الكود أعلاه، ستشاهد تسلسلاً من المخرجات في وحدة التحكم (console) يوضح كيفية تدفق البيانات عبر Stream، وكيف يتم تحويلها، وتصفيتها، وكيف يتم التعامل مع الأخطاء. سيبدو الإخراج مشابهاً لما يلي:
------------------------------------- بدء الدرس: التعامل مع تدفق البيانات (Streams) ------------------------------------- --- إضافة بيانات إلى Stream --- ✅ تم استقبال بيانات معدلة (زوجية): 2 ✅ تم استقبال بيانات معدلة (زوجية): 4 ✅ تم استقبال بيانات معدلة (زوجية): 6 ✅ تم استقبال بيانات معدلة (زوجية): 8 --- إرسال خطأ --- ❌ خطأ في التدفق المعدل: مشكلة في معالجة البيانات! --- إغلاق StreamController --- ✅ تم استقبال بيانات معدلة (زوجية): 10 ✅ تم استقبال بيانات معدلة (زوجية): 12 🎉 اكتمل التدفق المعدل بنجاح. ------------------------------------- انتهى الدرس. -------------------------------------