Provider — حزمة إدارة حالة لـ Flutter، أنشأها Remi Rousselet في 2019 كغلاف حول InheritedWidget. يحل Provider مشكلة تمرير البيانات لأسفل شجرة الـ widgets دون props drilling: أي widget يمكنه الوصول إلى الحالة عبر context.read<T>() أو context.watch<T>(). وفقًا pub.dev، Provider هو مدير الحالة الأكثر شعبية في Flutter بأكثر من 25 ألف إعجاب.
الرئيسية
Provider — حزمة لإدارة الحالة وحقن التبعيات في Flutter، مبنية فوق InheritedWidget. يوفر Provider كائنًا (حالة، خدمة، مستودع) في شجرة الـ widgets ويعيد بناء واجهة المستخدم تلقائيًا عند تغيير البيانات. على عكس الاستخدام المباشر لـ InheritedWidget، يزيل Provider كل الكود المتكرر: لا حاجة لكتابة فئة فرعية من InheritedWidget، أو إعداد طريقة ثابتة of()، أو إدارة التداخل.
Provider هو الطريقة الموصى بها رسميًا من Google لإدارة الحالة في Flutter (فريق Flutter، 2019-2023). الحزمة جزء من نظام Flutter البيئي وتتم صيانتها بواسطة فريق Flutter. عند إطلاقه، تم اقتراح Provider كبديل للمتغيرات العامة و InheritedWidget: أي كائن يمكن الوصول إليه من أي مكان دون تمريره عبر المنشئ.
وفقًا لاستبيان مجتمع Flutter 2025، يُستخدم Provider في 72% من تطبيقات Flutter. الأسباب الرئيسية لشعبيته: عتبة دخول منخفضة، دعم مدمج لـ ChangeNotifier، التوافق مع البنى الأخرى (MVVM، BLoC) وغياب التبعيات الخارجية.
ChangeNotifier — فئة مدمجة في Flutter تنفذ نمط المستمع (Listener). يخطر ChangeNotifier المشتركين بالتغييرات عبر استدعاء notifyListeners(). في سياق Provider، ChangeNotifier هو الفئة الرئيسية للحالة: يتم إنشاء فئة ترث ChangeNotifier بحقول وطرق تستدعي notifyListeners() بعد تغيير البيانات.
class CounterProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}قواعد notifyListeners: استدعاؤه بعد تغيير البيانات بالكامل — ليس في منتصف الطريقة بل في النهاية. إذا كانت الطريقة تنفذ تغييرات متعددة، استدع notifyListeners() مرة واحدة بعد كل التغييرات، وليس بعد كل تغيير. هذا يمنع عمليات إعادة الرسم المتعددة في خطوة منطقية واحدة. للتحديثات المجمعة، استخدم notifyListeners مع أنماط تشبه setState.
بدائل ChangeNotifier: ValueNotifier — لقيمة واحدة (جيد للأنواع البدائية)، StateNotifier — من حزمة state_notifier (نادر الاستخدام بمفرده). معظم حلول Provider تستخدم ChangeNotifier بسبب دعمه المدمج وبساطته.
Consumer — widget يشترك في ChangeNotifier ويعيد البناء عند كل استدعاء لـ notifyListeners(). يأخذ Consumer دالة builder بثلاث معاملات: context، model، child. Child — widget لا يعتمد على النموذج ولا يعيد Consumer بناءه. هذا تحسين: إذا كان Consumer يحتوي على widget ثابت (أيقونة، نص بدون بيانات)، يتم تمريره عبر child ولا يُعاد إنشاؤه.
Consumer<CounterProvider>(
builder: (context, provider, child) => Column(
children: [
child!, // لا يعاد بناؤه
Text('${provider.count}'),
ElevatedButton(
onPressed: () => provider.increment(),
child: Icon(Icons.add),
),
],
),
child: Text('العداد:'),
)context.watch — طريقة توسيع لـ BuildContext للاشتراك في Provider. تعيد النموذج وتشترك في تغييراته للـ widget الحالي. context.read — وصول دون اشتراك (لمعالجات onPressed، initState و dispose). context.select — اشتراك في حقل معين من النموذج دون إعادة البناء عند تغيير الحقول الأخرى. Select هو الخيار الأكثر كفاءة للنماذج المعقدة التي تحتوي على 10+ حقل.
متى تستخدم Consumer أو watch أو select: Consumer — عندما تحتاج widget child للتحسين. watch — في طريقة build للقراءة البسيطة. select — عندما يكون للنموذج عدة حقول但你 يعتمد الـ widget على حقل واحد فقط. يلغي Provider الاشتراك تلقائيًا عند تدمير الـ widget، مما يمنع تسرب الذاكرة.
MultiProvider — widget لتسجيل عدة Provider دون تداخل. بدلاً من شجرة بها 5 مستويات من Provider → Provider → Provider، يأخذ MultiProvider قائمة من المزودين. يمكن لكل Provider لاحق استخدام المزودين السابقين عبر المنشئ. MultiProvider هو الطريقة القياسية لتنظيم المستوى الجذر للتطبيق.
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartProvider()),
ChangeNotifierProvider(create: (_) => AuthProvider()),
ProxyProvider<AuthProvider, OrderProvider>(
update: (_, auth, __) => OrderProvider(auth.userId),
),
],
child: MaterialApp(home: HomePage()),
)ProxyProvider — Provider يعتمد على Provider آخر. يحصل ProxyProvider على القيم من مزودين آخرين ويمررها إلى كائنه. على سبيل المثال، OrderProvider يعتمد على AuthProvider (يحتاج userId). عندما يتغير AuthProvider، يعيد ProxyProvider إنشاء OrderProvider تلقائيًا مع userId الجديد. ChangeNotifierProxyProvider — نسخة ProxyProvider لـ ChangeNotifier.
StreamProvider و FutureProvider: StreamProvider يشترك في Stream (Firebase، WebSocket) ويحدث Consumer عند كل حدث جديد. FutureProvider — للتهيئة غير المتزامنة: يشغل Future، يعرض تحميلًا، ثم يمرر النتيجة إلى الـ widgets. كلاهما يحل المهام الشائعة دون إدارة يدوية للاشتراكات.
يُختبر Provider عبر تغليف الـ widget في MultiProvider بقيم اختبارية. لا حاجة لـ API أو قاعدة بيانات حقيقية للاختبار — يتم استبدال Provider بكائن وهمي. توفر حزمة provider ProviderScope لعزل الاختبارات — كل اختبار ينشئ شجرة Provider خاصة به بشكل مستقل.
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Counter increments on button tap',
(tester) async {
await tester.pumpWidget(
ChangeNotifierProvider(
create: (_) => CounterProvider(),
child: CounterScreen(),
),
);
await tester.tap(find.byKey(Key('increment')));
await tester.pump();
expect(find.text('1'), findsOneWidget);
},
);
}MockProvider: لاختبار الـ widgets التي تحتوي على Provider يعتمد على API، أنشئ فئة فرعية وهمية أو استخدم mockito / mocktail. لا يتطلب Provider أدوات محاكاة خاصة — أي كائن يرث ChangeNotifier يمكن تمريره عبر create دون استدعاء الخدمة الحقيقية. برمج Provider من خلال واجهات (فئة مجردة) لسهولة الاستبدال.
أداء Provider يعتمد على InheritedWidget: عندما يتغير Provider، يتم إعادة بناء جميع الـ widgets المشتركة عبر context.watch أو Consumer. لمنع إعادة الرسم غير الضرورية، استخدم context.select (اشتراك في حقل معين)، Consumer مع معامل child و const للـ widgets الثابتة. لا يعيد Provider بناء الفروع غير المشتركة في التغييرات.
| الطريقة | الاشتراك | إعادة البناء | الاستخدام |
|---|---|---|---|
| context.watch | النموذج الكامل | أي تغيير | Widgets بسيطة |
| Consumer | النموذج الكامل | أي تغيير | مع تحسين child |
| context.select | حقل معين | فقط عند تغيير الحقل | نماذج معقدة |
| context.read | لا | أبدًا | معالجات الأحداث |
قيود: لا يدعم Provider عزل منطق الأعمال على مستوى الحدث (مثل BLoC). جميع التغييرات تحدث عبر استدعاءات مباشرة لطرق ChangeNotifier، مما قد يؤدي إلى سلاسل تغييرات غير مسيطر عليها. للسيناريوهات المعقدة (عمليات غير متزامنة متعددة، تحقق معقد)، يقصر Provider مقارنة بـ BLoC و Riverpod.
الترحيل من Provider: يمكن دمج Provider بسهولة مع الحزم الأخرى. للترحيل إلى Riverpod، استخدم ChangeNotifierProvider.adaptive — محول يسمح باستخدام ChangeNotifiers الموجودة مع Riverpod دون إعادة كتابة. لـ BLoC — يمكن وضع BlocProvider داخل شجرة Provider، لاستبدال ChangeNotifier بـ Bloc تدريجيًا.
الأسئلة الشائعة
Provider — غلاف حول InheritedWidget لحقن التبعيات مع ChangeNotifier. BLoC — نمط معماري مع Event + Stream لعزل المنطق. Provider أسهل في التعلم، BLoC ينظم الكود بشكل أكثر صرامة. Provider مناسب للتطبيقات الصغيرة وحالة واجهة المستخدم، BLoC لمنطق الأعمال المعقد. وفقًا لمجتمع Flutter 2025، كلاهما يُستخدمان معًا غالبًا في نفس المشروع.
ChangeNotifierProvider — نوع من Provider لنسخ ChangeNotifier. ينشئ الكائن عبر create، يوفره للنسل ويعيد بناء Consumer عند استدعاء notifyListeners. يستدعي ChangeNotifierProvider تلقائيًا dispose على ChangeNotifier عند إزالته من الشجرة. توجد ثلاث طرق للإنشاء: ChangeNotifierProvider.value (لكائن موجود)، ChangeNotifierProvider (للإنشاء البطيء) و ChangeNotifierProvider.create (للإنشاء البطيء الصريح).
استخدم context.select بدلاً من context.watch — يعيد الـ widget البناء فقط عند تغيير الحقل المحدد. قسم ChangeNotifiers الكبيرة إلى عدة صغيرة (نموذج واحد — مسؤولية واحدة). استخدم Consumer child للأجزاء الثابتة. للقوائم، استخدم ListView.builder مع مفاتيح. يظهر Provider DevTools (Flutter Inspector) أي الـ widgets تعيد البناء ولماذا.
نعم. Provider (بدون ChangeNotifier) — لحقن الكائنات غير القابلة للتغيير (مستودع، عميل API، إعدادات). ValueListenableProvider — لـ ValueNotifier. StreamProvider — لـ Stream (Firebase، WebSocket). FutureProvider — لـ Future (تحميل الإعدادات عند البدء). ProxyProvider — لـ Provider التي تعتمد على Provider أخرى. ChangeNotifier مطلوب فقط للحالة القابلة للتغيير مع تحديث واجهة المستخدم.
ProviderNotFoundException — استثناء وقت التشغيل يحدث عند محاولة الحصول على Provider لم يتم تعريفه أعلى في شجرة الـ widgets. الأسباب الشائعة: Provider معرف في مستوى أدنى من الـ widget الذي يحاول قراءته؛ Provider معرف في مسار ويُقرأ في مسار آخر؛ خطأ إملائي في النوع. الحل: ارفع Provider لأعلى في الشجرة أو استخدم MultiProvider على مستوى MaterialApp للتبعيات العامة.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.