انتقل للمحتوى
علي هيثم·TECH·يردّ خلال أقل من ساعة
  • الرئيسيةابدأ من هنا
  • الأعمالدراسات حالة، مشاريع
  • الخدماتما الذي سأبنيه لك
  • المدونةمقالات، سلاسل
  • التدريبدورات، مختبرات، دورات
  • عنّيالمهندس وراء هذا الموقع
تسجيل الدخولابدأ مشروع
علي هيثم · TECH
  • الرئيسية↗
  • الأعمال↗
  • الخدمات↗
  • المدونة↗
  • التدريب↗
  • عنّي↗
تسجيل الدخولابدأ مشروع
متاح
علي هيثم·TECH

استوديو هندسي. دمشق، GMT+3.

aliyosef.online

الاستوديو

  • الأعمال
  • المدونة
  • التدريب
  • عنّي
  • تواصل معي

البوابة

  • تسجيل الدخول
  • فتح تذكرة دعم
  • متابعة مشروع
  • حسابي

مصادر

  • الوثائق
  • حالة الأنظمة
  • سجلّ التغييرات
  • حزمة العلامة
  • الخصوصية
  • الشروط

النشرة

ملاحظات تقنية وأخبار، أسبوعياً. بدون حشو.

مجاني. إلغاء الاشتراك متى ما حبيت.

تابعني على
© 2026 علي هيثم يوسف. جميع الحقوق محفوظة.مبنيّ يدويًا بـ React 19.آخر نشر · 2026-05-08كل الأنظمة تعمل
  1. الرئيسية/
  2. المدوّنة/
  3. Flutter
Flutter

Riverpod مقابل Bloc، بعد سنة

إيصالات من الإنتاج. وين بيناسب كل واحد، وين بيفشل، وشو تمنّينا حدا قاللنا.

١٠ شباط ٢٠٢٦·10 دقائق·بقلم علي يوسف

جاري تحميل المقال…

شاركXLinkedInRSS
استلم المقال التالي في بريدك.مقال جديد كل أحد. هندسة بلغة واضحة. مجاني، يمكنك إلغاء الاشتراك في أي وقت.اشترك→
AY
علي هيثم يوسف

مهندس وكاتب. أبني منصات متعدّدة المستأجرين، محرّكات مزامنة، وأنظمة موثوقة بهدوء. أكثر من ٦٠٠ مشروع مُسلَّم منذ ٢٠١٥.

اشتركابدأ مشروعاً→

اقرأ بعدها

  • OF
    Series
    ٦ آذار ٢٠٢٦·Flutter

    Flutter offline-first، الجزء الأول: الـ boot

    افتتاح السلسلة. شو بيعمل التطبيق بأول 800ms لما ما في شبكة.

    13 دقيقة
  • DM
    ٢٦ شباط ٢٠٢٦·Flutter

    Drift migrations بدون فقد بيانات

    القبل، الأثناء، الـ rollback. فحوصات pre-flight أنقذتنا خمس مرات.

    9 دقائق
  • SR
    ١٨ شباط ٢٠٢٦·Flutter

    تكلفة استعادة الحالة

    إرجاع التطبيق لمكان ما تركه المستخدم. النسخة الرخيصة مقابل الغالية.

    7 دقائق
←العودة إلى كل المقالات

قبل سنة كان لدينا تطبيقا Flutter حقيقيّان في الإنتاج، واحد على Bloc وآخر على Riverpod، كلاهما مكتوبان من نفس الفريق. هذا ما تعلّمناه. كلتا المكتبتَين جيّدتان. تُحسّنان لأشياء مختلفة وأحد تلك الأشياء يهمّ أكثر من الآخر لمعظم الفرق.

لماذا كلّ منهما فعلاً#

تخطّ التسويق. هذا الاختلاف البنيويّ:

  • Bloc يعامل الحالة كتيّار من الأحداث ينتج تيّاراً من الحالات. النموذج العقليّ هو «حدث إدخال داخلاً، حالة إخراجاً». رأيها صريح في كيف تكتب المنتج (فئة Bloc مع معالجي أحداث) والمستهلك (BlocBuilder / BlocListener).
  • Riverpod يعامل الحالة كرسم بيانيّ من providers، كلّ منها يحسب قيمة من providers أخرى. النموذج العقليّ هو «هذا الشيء يعتمد على ذاك الشيء يعتمد على ذلك الشيء الآخر». رأيها صريح في التفاعليّة والاعتماديّة، حياديّ في كيفيّة تحديث الحالة داخل provider.

Bloc يرسم خطّاً بين الأحداث والحالة والعرض. Riverpod يرسم رسماً بيانيّاً من القيم تتفرّع إلى العرض. كلاهما يعمل. الفرق هو ما يمنعانك من فعله.

أين Bloc يتألّق#

حالتان:

١. سير عمل بانتقالات مسمّاة#

إن كانت شاشتك آلة حالات (تدفّق OTP، خروج، معالج onboarding)، معالجو on<Event> في Bloc يرتبطون بنظافة بالحالات التي يمكن للمستخدم أن يكون فيها. قراءة ملفّ bloc هي قراءة آلة الحالات. أعضاء الفريق الجدد يستطيعون التنقّل فيها.

class OtpBloc extends Bloc<OtpEvent, OtpState> {
  OtpBloc() : super(const OtpInitial()) {
    on<OtpRequested>(_onRequested);
    on<OtpResent>(_onResent);
    on<OtpEntered>(_onEntered);
    on<OtpVerified>(_onVerified);
    on<OtpExpired>(_onExpired);
  }
}

الاختبارات مقابل Bloc جيّدة بشكل غير عاديّ: أرسل أحداثاً، أكّد تسلسل حالات، انتهى. حصلنا على > ٩٠٪ تغطية فروع على ملفّات Bloc لدينا بدون محاولة.

٢. ألفة التدقيق / event-sourcing#

حين تكون الأحداث نفسها قيّمة (التحليلات، التدقيق، إعادة العرض)، يعطيك Bloc إيّاها مجّاناً. وصلنا أحداث Bloc إلى مصرف تحليلات واحد وحصلنا على تتبّع تدفّق المستخدم بدون كود إضافيّ لكلّ شاشة.

أين Riverpod يتألّق#

ثلاث حالات:

١. حالة شاملة بأعمار دقيقة#

provider في Riverpod يمكن أن يكون autoDispose، مفتوحاً بالعائلة، محصوراً بمسار، محصوراً بالتطبيق. عمر الحالة في Riverpod مفهوم من الدرجة الأولى؛ في Bloc، تُغلّف providers حول شجرة الواجهة وتدعو.

final invoiceProvider = FutureProvider.family.autoDispose<Invoice, String>(
  (ref, id) async {
    final db = ref.watch(databaseProvider);
    return db.invoices.byId(id);
  },
);

الـ provider يُنشأ حين تحتاجه الشاشة، يُتخلَّص منه حين تغادر الشاشة، مفتوح بالعائلة بـ id كي لا تتصادم شاشتان مفتوحتان بـ id مختلفَين. فعل هذا في Bloc يتطلّب توسيع provider يدويّاً في شجرة الواجهة.

٢. حالة مشتقّة برسوم بيانيّة عميقة#

حين تعتمد قيمة الحالة B على A، و C يعتمد على A و B، و D يعتمد على B، رسم Riverpod البيانيّ يقرأ بنظافة:

final filtersProvider = StateProvider<InvoiceFilters>((ref) => InvoiceFilters.empty());
final allInvoicesProvider = Provider((ref) => ref.watch(databaseProvider).invoices.all);
final filteredInvoicesProvider = Provider((ref) {
  final filters = ref.watch(filtersProvider);
  final all = ref.watch(allInvoicesProvider);
  return all.where(filters.matches);
});
final summaryProvider = Provider((ref) {
  final filtered = ref.watch(filteredInvoicesProvider);
  return InvoiceSummary.from(filtered);
});

نفس الإعداد في Bloc يتطلّب أحداثاً تتدفّق عبر سلسلة من blocs أو bloc كبير واحد بفروع حالة كثيرة. كلتاهما طريقة بكود أكثر.

٣. حالة «أحتاج عدّاداً فقط»#

لحالة تافهة لن يحتاج أحد لاختبارها أبداً، StateProvider في Riverpod سطر واحد. الحدّ الأدنى لـ Bloc فئة Bloc بحدث واحد على الأقلّ وحالة واحدة على الأقلّ. الإجراءات تتراكم على مدى أسبوع.

أين تفشل كلّ منهما#

Bloc، بعد حوالي ٣٠ شاشة#

نمط Bloc «غلّف المستهلك بـ BlocProvider» يبدأ بتوليد أشجار واجهة بخمس طبقات providers فوق كلّ شاشة. كان لدينا ملفّات الـ MultiBlocProvider فيها أطول من بقيّة الشاشة. إرهاق مراجعة الكود حقيقيّ.

Riverpod، على الشاشات الثقيلة بسير العمل#

نموذج الرسم البيانيّ رائع حتّى تكون الشاشة في الأساس آلة حالات. كان لدينا تدفّق checkout مدعوم بـ Riverpod حيث كانت «الخطوة الحاليّة» StateProvider، وكلّ انتقال يقلبها يدويّاً. قراءة ذلك التدفّق بعد ستّة أشهر كانت أصعب من قراءة bloc. الانتقالات كانت مبعثرة عبر الملفّ.

ما انتهينا بفعله#

الدرس ليس «اختر واحدة». هو «اختر لكلّ سطح».

السطحالمكتبة
ذواكر المجال (الفواتير، جهات الاتّصال، المنتجات)Riverpod
الإعدادات، تفضيلات المستخدمRiverpod
تدفّق المصادقة، OTP، onboardingBloc
Checkout، نماذج متعدّدة الخطواتBloc
لوحات الفلتر، نتائج البحثRiverpod
حالة الاتّصال / المزامنةRiverpod
حالة محلّيّة بسيطة للشاشة (عدّادات، toggles)Riverpod

الفرق التي تتجادل حول «أيّهما يفوز» تميل إلى أن تكون فرقاً التزمت بواحدة للتطبيق كلّه. حالما تقبل أنّ الأسطح المختلفة لها أشكال مختلفة، الاختيار يصبح أسهل.

ما تمنّينا لو قيل لنا#

ثلاثة أشياء، مع سنة من المعاينة:

  1. كلتا المكتبتَين ممتازتان. الاختيار «الخاطئ» يكلّف ٣٠٪ على الأكثر من حجم الكود على الأسطح التي يناسبها أقلّ. الاختيار الصحيح في كلّ مكان يفوز ٠٪ في الغالب لأنّ كلتيهما تعملان.
  2. اختبر أصعب شاشة أوّلاً. اقضِ يوماً ببناء شاشة واحدة في كلّ مكتبة. الشاشة التي يسهل قراءتها بعد ستّة أشهر هي الاختيار الصحيح لذلك السطح. عمّم بحذر.
  3. الترحيل بينهما رخيص. provider Riverpod يمكن تغليفه ليبدو كمستهلك Bloc والعكس بالعكس. رحّلنا ثلاث شاشات بين الاثنتَين على مدى سنة ولم نخسر يوماً عليه. الاختيار ليس باباً ذا اتّجاه واحد.

الجزء التالي من هذه السلسلة يقفز إلى سطح مختلف: الهندسة المقترنة بالـ AI في الإنتاج، كيف تبدو فعلاً خارج العرض. أو راجع صفحة السلسلة لبقيّة قوس offline-Flutter.