Riverpod مقابل Bloc، بعد سنة
إيصالات من الإنتاج. وين بيناسب كل واحد، وين بيفشل، وشو تمنّينا حدا قاللنا.
جاري تحميل المقال…
إيصالات من الإنتاج. وين بيناسب كل واحد، وين بيفشل، وشو تمنّينا حدا قاللنا.
جاري تحميل المقال…
قبل سنة كان لدينا تطبيقا Flutter حقيقيّان في الإنتاج، واحد على Bloc وآخر على Riverpod، كلاهما مكتوبان من نفس الفريق. هذا ما تعلّمناه. كلتا المكتبتَين جيّدتان. تُحسّنان لأشياء مختلفة وأحد تلك الأشياء يهمّ أكثر من الآخر لمعظم الفرق.
تخطّ التسويق. هذا الاختلاف البنيويّ:
Bloc مع معالجي أحداث) والمستهلك
(BlocBuilder / BlocListener).Bloc يرسم خطّاً بين الأحداث والحالة والعرض. Riverpod يرسم رسماً بيانيّاً من القيم تتفرّع إلى العرض. كلاهما يعمل. الفرق هو ما يمنعانك من فعله.
حالتان:
إن كانت شاشتك آلة حالات (تدفّق 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 لدينا بدون محاولة.
حين تكون الأحداث نفسها قيّمة (التحليلات، التدقيق، إعادة العرض)، يعطيك Bloc إيّاها مجّاناً. وصلنا أحداث Bloc إلى مصرف تحليلات واحد وحصلنا على تتبّع تدفّق المستخدم بدون كود إضافيّ لكلّ شاشة.
ثلاث حالات:
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 «غلّف المستهلك بـ BlocProvider» يبدأ بتوليد أشجار واجهة
بخمس طبقات providers فوق كلّ شاشة. كان لدينا ملفّات الـ MultiBlocProvider
فيها أطول من بقيّة الشاشة. إرهاق مراجعة الكود حقيقيّ.
نموذج الرسم البيانيّ رائع حتّى تكون الشاشة في الأساس آلة حالات. كان
لدينا تدفّق checkout مدعوم بـ Riverpod حيث كانت «الخطوة الحاليّة»
StateProvider، وكلّ انتقال يقلبها يدويّاً. قراءة ذلك التدفّق بعد
ستّة أشهر كانت أصعب من قراءة bloc. الانتقالات كانت مبعثرة عبر الملفّ.
الدرس ليس «اختر واحدة». هو «اختر لكلّ سطح».
| السطح | المكتبة |
|---|---|
| ذواكر المجال (الفواتير، جهات الاتّصال، المنتجات) | Riverpod |
| الإعدادات، تفضيلات المستخدم | Riverpod |
| تدفّق المصادقة، OTP، onboarding | Bloc |
| Checkout، نماذج متعدّدة الخطوات | Bloc |
| لوحات الفلتر، نتائج البحث | Riverpod |
| حالة الاتّصال / المزامنة | Riverpod |
| حالة محلّيّة بسيطة للشاشة (عدّادات، toggles) | Riverpod |
الفرق التي تتجادل حول «أيّهما يفوز» تميل إلى أن تكون فرقاً التزمت بواحدة للتطبيق كلّه. حالما تقبل أنّ الأسطح المختلفة لها أشكال مختلفة، الاختيار يصبح أسهل.
ثلاثة أشياء، مع سنة من المعاينة:
الجزء التالي من هذه السلسلة يقفز إلى سطح مختلف: الهندسة المقترنة بالـ AI في الإنتاج، كيف تبدو فعلاً خارج العرض. أو راجع صفحة السلسلة لبقيّة قوس offline-Flutter.