مكونات flutter_bloc: BlocProvider و BlocBuilder
مكونات flutter_bloc: BlocProvider و BlocBuilder
تأتي حزمة flutter_bloc مزودةً بمجموعة من الودجات التي تربط كلاسات BLoC بشجرة الودجات في Flutter. أهم ودجتين أساسيتين هما BlocProvider الذي يحقن BLoC ليتمكن أي عنصر تابع من الوصول إليه، وBlocBuilder الذي يعيد بناء الشجرة الفرعية التي يلفّها فحسب عند كل انبعاث حالة جديدة من BLoC. إتقان هاتين الودجتين هو الخطوة العملية الأولى نحو كتابة واجهات BLoC نظيفة وقابلة للاختبار وتفاعلية.
BlocProvider: حقن التبعيات عبر شجرة الودجات
BlocProvider ودجت مبنية على InheritedWidget تنشئ BLoC (أو Cubit) وتتيحه لجميع الودجات التابعة لها في الشجرة. تمتلك هذه الودجت مثيل BLoC وتستدعي تلقائياً close() عليه حين إزالة المزوّد من الشجرة، مما يمنع تسرب الذاكرة.
- create — مصنع يستقبل
BuildContextويعيد مثيلاً جديداً من BLoC. - child — الشجرة الفرعية التي ستتمكن من الوصول إلى BLoC.
- lazy — قيمته الافتراضية
true؛ يُنشأ BLoC فقط عند أول وصول إليه.
توفير CounterCubit لشاشة
// main.dart — لفّ المسار أو الشاشة في BlocProvider
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_cubit.dart';
import 'counter_page.dart';
class CounterRoute extends StatelessWidget {
const CounterRoute({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CounterCubit(), // يُنشأ هنا ويُعاد هنا
child: const CounterPage(),
);
}
}
// counter_cubit.dart
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
BlocProvider.create، ولا تمرر مثيلاً منشأً مسبقاً تملكه في مكان آخر — فذلك يكسر عقد الإتلاف التلقائي. إن احتجت مشاركة مثيل موجود (مثل تمريره بين المسارات)، استخدم BlocProvider.value وأدر دورة حياته بنفسك.BlocBuilder: إعادة بناء UI تفاعلية ومحددة الهدف
BlocBuilder<B, S> يستمع إلى BLoC من النوع B الذي يبعث حالات من النوع S. في كل مرة تُبعث حالة جديدة، يُستدعى callback المسمى builder مع BuildContext الحالي وأحدث حالة، ويُعاد بناء الشجرة الفرعية التي تعيدها فقط — دون المساس بأي شيء فوقها.
- builder — مطلوب؛ يستقبل
(context, state)ويعيدWidget. - buildWhen — تسلسل اختياري
(previous, current) => bool؛ أعدfalseلتخطي إعادة البناء حين لم تتغير أجزاء الحالة ذات الصلة. - bloc — اختياري؛ إن حُذف بحث
BlocBuilderعن أقربBlocProviderمطابق للنوع عبرcontext.read.
BlocBuilder يعيد بناء شاشة العداد فقط
// counter_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_cubit.dart';
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('عداد BloC')),
body: Center(
// هذه الشجرة الفرعية فقط تُعاد بناؤها عند انبعاث CounterCubit
child: BlocBuilder<CounterCubit, int>(
builder: (context, count) {
return Text(
'$count',
style: Theme.of(context).textTheme.displayLarge,
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
// context.read لا يشترك — آمن في callbacks
onPressed: () => context.read<CounterCubit>().increment(),
child: const Icon(Icons.add),
),
const SizedBox(height: 8),
FloatingActionButton(
onPressed: () => context.read<CounterCubit>().decrement(),
child: const Icon(Icons.remove),
),
],
),
);
}
}
context.read مقابل context.watch
تُتيح حزمة flutter_bloc امتدادين مريحين على BuildContext يكمّلان الودجات:
- context.read<T>() — يبحث عن BLoC أو Cubit من النوع
Tفي شجرة الودجات دون الاشتراك. استخدمه داخل معالجات الأحداث ودوال callback للأزرار وinitState— في أي مكان تستدعي فيه طريقة على BLoC دون الحاجة إلى إعادة بناء. - context.watch<T>() — يبحث عن BLoC ويشترك في تدفقه، مما يجعل الودجت الحالية تُعاد بناؤها عند كل حالة جديدة. يعادل لفّ الودجت في
BlocBuilder. فضّلBlocBuilderلإعادة البناء الدقيقة، واستخدمcontext.watchحين يكون إعادة بناء الودجت بالكامل مقبولاً.
context.watch داخل callback أو initState أو أي طريقة ليست build. إنها تسجّل اشتراكاً على BuildContext وستطرح استثناءً إن استُدعيت خارج مرحلة البناء. استخدم context.read في تلك الحالات.buildWhen: تحكم دقيق في إعادة البناء
حين يكبر كائن حالتك، يمكنك تفادي إعادة البناء غير الضرورية بتقديم تسلسل buildWhen. يُستدعى builder فقط حين يعيد التسلسل true.
تخطي إعادات البناء باستخدام buildWhen
// لنفترض أن الحالة كلاس أكثر تعقيداً
class ShopState {
final int cartCount;
final bool isLoading;
final List<String> items;
const ShopState({
required this.cartCount,
required this.isLoading,
required this.items,
});
}
// شارة السلة تهتم فقط بتغييرات cartCount
BlocBuilder<ShopBloc, ShopState>(
buildWhen: (previous, current) =>
previous.cartCount != current.cartCount,
builder: (context, state) {
return Badge(
label: Text('${state.cartCount}'),
child: const Icon(Icons.shopping_cart),
);
},
)
BlocBuilder في أعمق نقطة ممكنة في شجرة الودجات، مالفّاً فقط الودجات التي تعتمد فعلياً على الحالة. إذا وُضع BlocBuilder في أعلى الشجرة فسيُعيد بناء شجرة فرعية ضخمة عند كل انبعاث حالة — وهو نفس خطأ الأداء الناتج عن إساءة استخدام setState على ودجت أصل.الخلاصة
استخدم BlocProvider لحقن BLoC في الشجرة الفرعية التي تحتاجه، مستفيداً من إدارة دورة الحياة التلقائية. استخدم BlocBuilder لإعادة بناء الودجت المعتمدة على حالة BLoC فحسب، وأمدّه بـbuildWhen لتخطي إعادات البناء حين لا تتغير شريحة الحالة ذات الصلة. الجأ إلى context.read في callbacks الأحداث وإلى context.watch (أو BlocBuilder) في طريقة البناء. معاً توفر هاتان الودجتان جسراً واضحاً وقابلاً للاختبار وعالي الأداء بين منطق عملك وواجهة المستخدم.