لماذا Riverpod؟
بدرس إدارة الحالة تعرّفنا على Provider كأول خطوة لمشاركة الحالة بين ويدجتس
متعدّدة. Riverpod حزمة مستقلّة من نفس مطوّر Provider، صُمِّمت لتحلّ
محدوديات معيّنة فيه:
- لا يعتمد على
BuildContextللقراءة — يمكن قراءة أي Provider من أي مكان بأمان (حتى خارج شجرة الويدجتس، مثل داخل دالة اختبار). - يكتشف أخطاء استخدام شائعة وقت الترجمة (compile-time) بدل اكتشافها وقت التشغيل فقط.
- يسهّل دمج عدّة مصادر حالة ببعضها (Provider يعتمد على Provider آخر).
التثبيت والإعداد
flutter pub add flutter_riverpod
لفّ جذر التطبيق بـ ProviderScope — هنا تُخزَّن حالة كل الـ Providers:
void main() {
runApp(const ProviderScope(child: MyApp()));
}
Provider بسيط للقراءة فقط
final appNameProvider = Provider<String>((ref) => 'The Code Fix');
استهلاكه داخل ويدجت يرث من ConsumerWidget بدل StatelessWidget:
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final name = ref.watch(appNameProvider);
return Scaffold(body: Center(child: Text(name)));
}
}
حالة قابلة للتعديل — NotifierProvider
للحالة التي تتغيّر (عدّاد، قائمة مهام)، تُعرَّف عبر صنف يرث Notifier:
final counterProvider =
NotifierProvider<CounterNotifier, int>(CounterNotifier.new);
class CounterNotifier extends Notifier<int> {
@override
int build() => 0; // القيمة الابتدائية
void increment() => state = state + 1;
void decrement() => state = state - 1;
}
الاستخدام بالواجهة — ref.watch لإعادة البناء عند التغيّر، وref.read
لاستدعاء دالة بلا اشتراك بالتغيّرات:
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(child: Text('العدّاد: $count')),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}
💡 استخدم
ref.watchداخلbuild()فقط — لاستدعاء دالة تُغيّر الحالة (مثل عند الضغط على زر) استخدمref.readبدلًا منه.
Provider مقابل Riverpod
| المقياس | Provider | Riverpod |
|---|---|---|
| الاعتماد على BuildContext | نعم (context.watch/read) | لا — يعمل بلا context |
| اكتشاف الأخطاء | وقت التشغيل غالبًا | وقت الترجمة بمعظم الحالات |
| منحنى التعلّم | أبسط للبداية | مفاهيم إضافية (ref, Notifier) |
أخطاء شائعة
- نسيان تغليف التطبيق بـ
ProviderScope— أي محاولة قراءة Provider تفشل فورًا. - استدعاء
ref.watchداخل دالة زر (onPressed) بدلref.read— يشترك بحالة غير ضرورية ويعيد بناء الويدجت بلا داعٍ.
🎯 التالي: التنقّل الحديث باستخدام go_router.