BLoC (Business Logic Component) — نمط إدارة حالة لتطبيقات Flutter، قدمته Google في 2018 في DartConf. يفصل BLoC منطق الأعمال عن واجهة المستخدم من خلال التدفقات التفاعلية (Stream): ترسل UI حدث Event، يعالجه BLoC ويعيد State جديد عبر Stream. وفقًا pub.dev، حصلت حزمة flutter_bloc على أكثر من 11 ألف إعجاب وتستخدم في آلاف تطبيقات Flutter.
أهم النقاط
BLoC (Business Logic Component) — نمط معماري لـ Flutter يتم فيه استخراج منطق الأعمال في صنف منفصل، معزول عن UI. يستقبل BLoC بيانات الدخل من خلال تدفق من الأحداث (Event) وينتج بيانات المخرج من خلال تدفق من الحالات (State). طبقة العرض (Widget) تشترك فقط في تدفق State وتعرض UI، دون تنفيذ منطق الأعمال مباشرة أبدًا.
يعتمد مفهوم BLoC على البرمجة التفاعلية ونمط Observer. كل مكون BLoC هو وحدة منفصلة بعقد واضح: مجموعة معروفة من Events (ما يمكن أن يحدث) ومجموعة معروفة من States (ما يمكن عرضه). لا يمكن للمطور تغيير الحالة "بالصدفة" من UI — فقط من خلال Event محدد. وهذا يجعل الكود قابلًا للتوقع والاختبار.
وفقًا لاستطلاع Flutter Community 2025، يحتل BLoC المرتبة الثانية من حيث الشعبية بين حلول إدارة الحالة في Flutter بعد Provider. المزايا الرئيسية: كتابة قوية (strong typing)، عزل المنطق، دعم مدمج لـ Stream، نظام بيئي غني من الأدوات (BlocProvider، BlocListener، BlocSelector).
هندسة BLoC تُبنى حول ثلاث كيانات: Event (دخل)، Bloc (معالج) و State (مخرج). يرسل Widget حدث Event عبر طريقة add(). يستقبل Bloc الحدث Event في طريقة mapEventToState أو on<Event>، ينفذ منطق الأعمال ويصدر State جديد عبر yield. يستقبل Widget الحالة State عبر Stream ويعيد بناء نفسه.
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
@override
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) {
yield state + 1;
} else if (event is Decrement) {
yield state - 1;
}
}
}سلامة الأنواع: Bloc مُوسَم بنوعين — Event و State. يتحقق مترجم Dart من أن Widget يستدعي فقط Events المعلنة وأن Bloc يعيد فقط States المعلنة. أخطاء وقت التشغيل مثل "إجراء غير معروف" مستبعدة.
الإغلاق والتخلص (Close and Dispose): ينفذ Bloc واجهة Closeable. عندما يتم تدمير أداة، يغلق Bloc تلقائيًا Stream عبر طريقة close(). تسرب الاشتراكات التفاعلية مستحيل — يدير BlocProvider دورة حياة Bloc، ويربطه بمسار أو صفحة.
Cubit هو تنفيذ مبسط لـ Bloc بدون Event، تم تقديمه في حزمة flutter_bloc 6.0. يعلن Cubit دوال مباشرة بدلاً من أصناف Event: increment()، fetchData(). داخليًا، يستخدم Cubit نفس الآلية القائمة على Stream ولكنه يخفي طبقة Event. هذا يقلل من boilerplate بنسبة 40-50% للسيناريوهات البسيطة.
| الخاصية | Bloc | Cubit |
|---|---|---|
| أصناف Event | إلزامية | غير مطلوبة |
| Boilerplate | مرتفع | منخفض |
| تتبع الإجراءات | عبر نوع Event | اسم الدالة فقط |
| مناسب لـ | السيناريوهات المعقدة | الحالات البسيطة |
| التحليلات | تلقائية عبر Event | يدوية |
متى تختار Cubit: حالة بها 2-3 متغيرات (تحميل، تم التحميل، خطأ)، نماذج بسيطة، عدادات، حالات UI (مفتوح/مغلق). متى تختار Bloc: منطق أعمال معقد مع إجراءات متعددة: معالجة الطلبات، التفويض، مزامنة البيانات. يوفر Bloc تتبعًا تفصيليًا لكل إجراء عبر Event — يتم تسجيل كل استدعاء في BlocObserver.
BlocObserver — مراقب عالمي يتتبع جميع Bloc و Cubit في التطبيق. يسمح بتسجيل Event و State والأخطاء والتحولات. ما عليك سوى توصيل نسخة واحدة: Bloc.observer = AppBlocObserver()، ويصبح تتبع حالة التطبيق بالكامل متاحًا مركزيًا.
BlocProvider — InheritedWidget من flutter_bloc يوفر Bloc للأدوات التابعة. عند تهيئة أداة، ينشئ BlocProvider Bloc، وعند تدميرها — يغلقه تلقائيًا عبر close(). يمكن وضع BlocProvider على مستوى MaterialApp (Bloc عام) أو على مستوى مسار محدد (Bloc محلي).
BlocProvider(
create: (context) => CounterBloc(),
child: Column(
children: [
BlocBuilder<CounterBloc, int>(
builder: (context, state) => Text('$state'),
),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Text('+'),
),
],
),
)BlocBuilder — أداة تعيد بناء UI عند كل State جديد. BlocListener — للتأثيرات الجانبية (معالجة State مرة واحدة دون إعادة بناء UI): إظهار SnackBar، التنقل إلى شاشة أخرى. BlocConsumer — مزيج من Builder و Listener للحالات التي تحتاج كلاً من إعادة البناء والتأثير الجانبي. BlocSelector — لإعادة بناء انتقائية فقط عند تغيير حقل معين من State.
MultiBlocProvider — أداة لـ BlocProviders المتداخلة دون زيادة مستويات التداخل. تطبيق Flutter به 10-15 Bloc يستخدم MultiBlocProvider على المستوى الجذري لتسجيل جميع Blocs المتاحة للتطبيق بأكمله: AuthenticationBloc، CartBloc، SettingsBloc.
يتم اختبار BLoC بشكل معزول بدون أدوات Flutter. ما عليك سوى استيراد حزمة Dart flutter_test والحزمة bloc_test. سيناريو الاختبار: إنشاء Bloc، إضافة Event، التحقق من State. blocTest — أداة تؤتمت التسلسل: build → act → expect.
blocTest<CounterBloc, int>(
'emits [1] when Increment is added',
build: () => CounterBloc(),
act: (bloc) => bloc.add(Increment()),
expect: () => [1],
)المحاكاة (Mocking): يتم اختبار Bloc الذي يعتمد على مستودع أو API باستخدام المحاكاة عبر mocktail. يتم محاكاة المستودع على مستوى التجريد، ويستقبل Bloc التبعيات المحاكاة عبر المنشئ. Hydrated Bloc — امتداد للحفظ/الاستعادة التلقائي للحالة في التخزين المحلي. يتم اختباره باستخدام HydratedBlocStorage وتخزين ملفات مؤقت.
المجلدات والملفات: هيكل مشروع Flutter نموذجي مع BLoC: bloc/counter_bloc.dart، bloc/counter_event.dart، bloc/counter_state.dart. لـ 30+ شاشة، يُوصى بالتجميع حسب الميزات: features/auth/bloc/، features/cart/bloc/. كل Bloc في ملف منفصل، كل Event و State — إما في ملفات منفصلة أو في ملف واحد مع Bloc.
الأداء: BLoC لا يخلق عبئًا إضافيًا على Streams الفارغة. يستخدم BlocBuilder buildWhen لتصفية إعادة البناء — يتم تحديث الأداة فقط عندما يتغير شرط محدد. يضمن Close أن Blocs غير النشطة لا تستهلك ذاكرة. وفقًا لـ Flutter DevTools، يضيف BLoC أقل من 1% إلى حجم الحزمة.
الترحيل من Provider: يتعايش BLoC بسهولة مع Provider في نفس المشروع. ترحيل تدريجي: أولاً استبدل Providers الأكثر تعقيدًا بـ Bloc، ثم الباقي. BlocProvider متوافق مع شجرة Provider: الأدوات القديمة يمكنها استخدام Provider، والأدوات الجديدة — BlocProvider، داخل تطبيق واحد.
الأسئلة الشائعة
BLoC يستخدم Event + Stream لعزل منطق الأعمال والكتابة القوية. Provider هو غلاف حول InheritedWidget لحقن تبعيات بسيط و ChangeNotifier. BLoC أفضل للسيناريوهات المعقدة مع حالات متعددة، Provider — لحالة UI المحلية. يتطلب BLoC المزيد من boilerplate لكنه يوفر تتبعًا كاملاً عبر Events.
Hydrated Bloc هو امتداد من حزمة hydrated_bloc، يحفظ تلقائيًا آخر State في التخزين المحلي (Hive افتراضيًا). عند إعادة تشغيل التطبيق، يستعيد Bloc الحالة المحفوظة بدلاً من الحالة الأولية. هذا يحل مشكلة الاستمرارية دون استدعاء حفظ يدوي: تسجيل الدخول، سلة التسوق، الإعدادات تُحفظ تلقائيًا بين الجلسات.
يتم التعامل مع الخطأ في BLoC من خلال try-catch داخل mapEventToState أو on<Event>. عند الخطأ، يعيد Bloc State خطأ: yield LoadError(error.message). على UI، يتحقق BlocListener أو BlocConsumer من State لنوع الخطأ ويعرض SnackBar أو حوارًا. يسجل BlocObserver عالميًا جميع الاستثناءات غير المعالجة.
BLoC هو نمط خاص بـ Flutter لأنه يستخدم Dart Stream وأدوات Flutter. يمكن تكييف مفهوم Event → Bloc → State لـ AngularDart و Server-side Dart، لكن النظام البيئي الرئيسي (BlocProvider، BlocBuilder، BlocObserver) مرتبط بـ Flutter. لـ React Native، استخدم Redux أو MobX؛ لـ SwiftUI، استخدم Combine + MVVM.
Cubit — للحالات البسيطة (عداد، toggle، نموذج به 2-3 حقول). Bloc — للمنطق المعقد (خلاصة أخبار، معالجة طلبات، تفويض). القاعدة الرئيسية: إذا كنت بحاجة إلى تتبع كل إجراء (Event) للتحليلات أو التصحيح — اختر Bloc. إذا كانت الدوال التي تغير الحالة كافية — اختر Cubit. كلا النمطين يمكن أن يتعايشا في نفس المشروع.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.