شارك المقالة
شرح StreamBuilder في Flutter وإدارة تدفق الأحداث

شرح StreamBuilder في Flutter وإدارة تدفق الأحداث

شرح StreamBuilder في Flutter لعرض أحداث Stream وحالات الانتظار والخطأ والانتهاء، مع تثبيت المصدر وإغلاق الموارد التي تملكها الصفحة.

شرح StreamBuilder في Flutter لعرض أحداث Stream وحالات الانتظار والخطأ والانتهاء، مع تثبيت المصدر وإغلاق الموارد التي تملكها الصفحة.

Stream يصف أكثر من نتيجة

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

ملكية المصدر مهمة

أنشئ أو احصل على Stream خارج build حتى لا يتغير المصدر مع إعادة بناء الأب دون قصد. إذا كانت الصفحة تنشئ StreamController فهي مسؤولة عن إغلاقه في دورة حياتها؛ أما تدفق تملكه خدمة مشتركة فلا تغلقه من صفحة واحدة. يدير StreamBuilder اشتراكه مع المصدر، لكن ذلك لا يعفيك من إدارة موارد المنتج أو أي listen إضافي أنشأته يدويًا. فصل الملكية يمنع بقاء listeners غير مستخدمة أو توقف خدمة يعتمد عليها أكثر من مكوّن.

قراءة snapshot دون فرض قيمة

في بداية الاستماع قد لا توجد بيانات، ويمكن initialData توفير قيمة مناسبة إذا كان معناها صحيحًا في التطبيق. اعرض الخطأ بصورة مختلفة عن غياب البيانات، وانتبه إلى ConnectionState.done عندما ينتهي المصدر. لا تفترض أن ظهور آخر قيمة يعني استمرار الاتصال. عند تبديل Stream قد توجد قيمة سابقة في snapshot وفق تسلسل الحالة؛ قرر هل إبقاؤها مفيد للمستخدم أم يجب إظهار انتقال واضح بين الموردين.

Single subscription وBroadcast

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

ما الذي تختبره؟

اختبر بداية الشاشة قبل أول حدث، ثم إرسال أكثر من حدث، ثم الخطأ والانتهاء في نسخة تجريبية. لا تنفذ SnackBar أو Navigator.pop داخل builder لمعالجة كل حدث، لأن البناء ليس تعهدًا بتنفيذ أثر مرة واحدة. إذا كان الحدث يتطلب إجراءً منفصلًا فاستخدم طبقة تستقبل الحدث مع سياسة واضحة لمنع التكرار. راقب إغلاق الصفحة وإنشاءها مجددًا للتأكد أن الموارد القديمة أغلقت وأن المصدر الجديد يبدأ بحالة مفهومة.

هل تحتاج كل حدث أم آخر حالة؟

واجهة العدد في المثال تحتاج أحدث قيمة فقط، أما نظام تسجيل أحداث أو حساب إجمالي مالي فيحتاج معالجة كل حدث وفق ترتيب وقواعد ثابتة. لا تنقل هذا الحساب إلى builder لأن جدولة الرسم قد تجمع آثار أكثر من حدث في لقطة واحدة. إذا كانت الأحداث سريعة، فكر في تقليل تكرار العرض مع الاحتفاظ بمعالجة المجال مستقلة، وراقب حجم القوائم أو المخازن التي تكبر دون حد. عند وجود أكثر من مستمع افحص هل يملك المصدر سلوك إعادة بث أم أنه يبث الجديد فقط، ووثّق كيف يحصل المشترك المتأخر على الحالة الأولية. اختبر فصل الاشتراك وإعادته وتغير الحساب النشط؛ لا تجعل listener قديمًا يستمر في إرسال بيانات حساب سابق إلى شاشة جديدة. تنظيف الموارد يمنع هذا الالتباس إلى جانب تقليل استهلاك الذاكرة.

مثال كامل للتجربة

ضع المثال في ملف مستقل داخل مشروع تجريبي. في أمثلة Flutter شغّل DemoPage داخل MaterialApp، ولا تدمج أكثر من main في الملف نفسه. البيانات محلية للتعلم ويمكن استبدالها بمصدر بيانات بعد فهم حالات النجاح والخطأ.

import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: DemoPage()));
class DemoPage extends StatefulWidget {
  const DemoPage({super.key});
  @override
  State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
  final _controller = StreamController<int>();
  int _value = 0;
  @override
  void dispose() { unawaited(_controller.close()); super.dispose(); }
  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(title: const Text('StreamBuilder')),
    body: Center(child: StreamBuilder<int>(
      stream: _controller.stream,
      initialData: 0,
      builder: (context, snapshot) {
        if (snapshot.hasError) return const Text('Stream error');
        return Text('Value: ${snapshot.data ?? 0}');
      },
    )),
    floatingActionButton: FloatingActionButton(
      onPressed: () => _controller.add(++_value),
      child: const Icon(Icons.add),
    ),
  );
}

مرجع التفاصيل والتوافق

راجع التوثيق الرسمي عند نقل المثال إلى إصدار مختلف، ثم جرّب الحالة الطبيعية والفشل ومغادرة الصفحة أثناء الانتظار. دوّن السلوك المتوقع قبل تعديل منطق مشروعك.

الصور والفيديو من الشرح السابق

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

How to use Stream with Ui in Flutter
شاهد أيضًا
مقالات ذات صلة
🚫 مانع الإعلانات مفعل

يجب إيقاف مانع الإعلانات لاستكمال تصفح الموقع