مكونات flutter_bloc: BlocListener و MultiBlocProvider
مكونات flutter_bloc: BlocListener و MultiBlocProvider
تأتي حزمة flutter_bloc مع مجموعة من الودجات المبنية لأغراض محددة تغطي كل نمط تفاعل تحتاجه عند العمل مع BLoCs. في هذا الدرس نركز على الثلاثة الأكثر إساءةً للفهم: BlocListener، وBlocConsumer، وMultiBlocProvider. فهم سبب وجود كل ودجت لا يقل أهمية عن معرفة كيفية استخدامه.
BlocListener — التفاعلات ذات الآثار الجانبية فقط
صُمِّم BlocListener للـآثار الجانبية ذات المرة الواحدة التي يجب ألا تطلق إعادة بناء لواجهة المستخدم أبدًا: الانتقال بين الصفحات، وعرض ScaffoldMessenger.showSnackBar، ونوافذ الحوار، وأحداث التحليلات. يُطلَق رد النداء listener في كل مرة يُصدر فيها الـ bloc حالة جديدة، لكنه لا يُصيِّر أي ودجت بذاته — فهو ببساطة يلف عنصراً child.
BlocBuilder. إذا كان يجب أن يطلق أثرًا جانبيًا بدون إعادة بناء، استخدم BlocListener. خلط الآثار الجانبية داخل رد نداء builder في BlocBuilder هو نمط مضاد شائع يسبب استدعاءات مكررة.BlocListener — مثال على التنقل وعرض SnackBar
BlocListener<AuthBloc, AuthState>(
// اختياري: يُطلق فقط عندما يكون هذا الشرط صحيحًا
listenWhen: (previous, current) =>
previous.status != current.status,
listener: (context, state) {
if (state.status == AuthStatus.authenticated) {
// انتقل بدون إعادة بناء الودجت الحالي
Navigator.of(context).pushReplacementNamed('/home');
} else if (state.status == AuthStatus.error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'Login failed')),
);
}
},
child: const LoginFormWidget(),
)
المحدد الاختياري listenWhen يعكس buildWhen في BlocBuilder: يستقبل الحالة السابقة والحالية ويجب أن يُرجع true فقط عندما يجب إطلاق المستمع. استخدمه لتجنب التفاعل مع كل إصدار عندما تهتم فقط بانتقال حقل محدد.
BlocConsumer — البناء والاستماع معًا
BlocConsumer هو ودجت مريح يجمع BlocBuilder وBlocListener في ودجت واحد عندما تحتاج كليهما — إعادة بناء لواجهة المستخدم وأثرًا جانبيًا — استجابةً لنفس تغيير الحالة. يقبل builder، وlistener، وbuildWhen، وlistenWhen.
BlocConsumer — إرسال نموذج مع مؤشر تحميل وSnackBar
BlocConsumer<RegistrationBloc, RegistrationState>(
listenWhen: (prev, curr) => curr.isFailure || curr.isSuccess,
listener: (context, state) {
if (state.isSuccess) {
Navigator.of(context).pushReplacementNamed('/dashboard');
}
if (state.isFailure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.errorMessage),
backgroundColor: Colors.red,
),
);
}
},
buildWhen: (prev, curr) => prev.isSubmitting != curr.isSubmitting,
builder: (context, state) {
return ElevatedButton(
onPressed: state.isSubmitting
? null
: () => context
.read<RegistrationBloc>()
.add(const FormSubmitted()),
child: state.isSubmitting
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Register'),
);
},
)
MultiBlocProvider — تركيب BLoCs عند جذر التطبيق
التطبيقات الحقيقية لديها BLoCs كثيرة. تداخل عدة BlocProviders يخلق شجرة ودجات ذات مسافة بادئة عميقة يصعب قراءتها. يُسطِّح MultiBlocProvider تلك البنية بقبوله قائمة providers. كل إدخال هو BlocProvider، وتُنشأ بترتيب القائمة. النتيجة مطابقة لتداخلها يدويًا، لكنها أكثر قابلية للقراءة بكثير.
MultiBlocProvider عند جذر التطبيق
// main.dart
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>(
create: (context) => AuthBloc(
authRepository: context.read<AuthRepository>(),
)..add(const AppStarted()),
),
BlocProvider<ThemeBloc>(
create: (context) => ThemeBloc(
settingsRepository: context.read<SettingsRepository>(),
),
),
BlocProvider<CartBloc>(
create: (context) => CartBloc(
cartRepository: context.read<CartRepository>(),
),
),
],
child: BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, themeState) => MaterialApp(
themeMode: themeState.mode,
theme: AppTheme.light,
darkTheme: AppTheme.dark,
home: const AppRouter(),
),
),
);
}
}
MultiBlocProvider بالقرب من الجذر كائنات مفردة (singletons) للشجرة الفرعية من الودجات الموجودة تحتها. تجنب إنشاء BLoCs ثقيلة الحمل في أعلى الشجرة إذا كانت مطلوبة فقط في شاشة واحدة. يُفضَّل استخدام أغلفة BlocProvider محددة النطاق على مستوى المسار للـ BLoCs الخاصة بالشاشة.MultiBlocListener و MultiRepositoryProvider
نفس نمط "Multi" يمتد إلى المستمعين وحقن المستودعات. يحل MultiBlocListener محل ودجات BlocListener المتداخلة على مستوى الموجِّه. أما MultiRepositoryProvider فيحقن كائنات Dart العادية للخدمات/المستودعات (بدون BLoC) في الشجرة بحيث يمكن قراءتها باستخدام context.read<T>().
الملخص
- BlocListener — للآثار الجانبية فقط (التنقل، الـ snackbars، الحوارات)؛ لا يعيد البناء أبدًا.
- BlocConsumer — إعادة بناء مدمجة مع أثر جانبي؛ استخدمه عندما تحتاج كليهما لنفس الإصدار.
- MultiBlocProvider — تركيب مسطّح وقابل للقراءة لعدة
BlocProviders؛ مثالي عند جذر التطبيق أو المسار. - استخدم
listenWhen/buildWhenلتصفية الإصدارات ومنع العمل غير الضروري.