تكرار السؤال: …
الإجابة المختصرة
يعلن Riverpod الحالة والتبعيات كـ providers مستقلة عن شجرة الـ Widgets، فيسهل اختبارها وتجاوزها، ويدير الحالات غير المتزامنة عبر AsyncValue (loading/data/error) وAsyncNotifier.
الشرح
يقرأ الـ provider غيره بـ ref.watch فيُعاد حسابه تلقائيًا عند تغيّر ما يعتمد عليه، بينما يستخدم ref.read داخل الأحداث لقراءة لمرة واحدة. يجمع AsyncValue حالات التحميل والنجاح والخطأ في نوع واحد يُعرض بـ when. وتتيح autoDispose تحرير الحالة عند انتهاء الحاجة، ويسمح ProviderScope.overrides باستبدال التبعيات في الاختبارات. اختره عندما تحتاج حالة مشتركة قابلة للاختبار بتبعيات واضحة، ولا تضف مكتبة كاملة لحالة محلية بسيطة تكفيها setState.
مثال بالكود
final userRepositoryProvider = Provider<UserRepository>(
(ref) => UserRepository(ref.watch(httpClientProvider)),
);
class UsersNotifier extends AsyncNotifier<List<User>> {
@override
Future<List<User>> build() => ref.watch(userRepositoryProvider).fetchUsers(); // re-runs if dependencies change
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(() => ref.read(userRepositoryProvider).fetchUsers());
}
}
final usersProvider = AsyncNotifierProvider<UsersNotifier, List<User>>(UsersNotifier.new);
class UsersPage extends ConsumerWidget {
const UsersPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final users = ref.watch(usersProvider);
return users.when(
data: (list) => ListView(children: [for (final u in list) Text(u.name)]),
loading: () => const CircularProgressIndicator(),
error: (error, _) => Text('Error: $error'),
);
}
}أخطاء شائعة
- استخدام ref.read داخل build بدل ref.watch، فلا تُعاد بناء الشاشة عند تغير الحالة.
- إنشاء providers داخل build أو الاحتفاظ بحالة كبيرة دائمة بدل استخدام autoDispose عند الحاجة.