في هذا الدرس سنتعلم Riverpod وطريقة التعامل معه في ادارة حالة البيانات في التطبيق، هذ الدرس لن يكون للمقارنة بين مكتبات ادارة حالة التطبيق، شرحت سابقا عن Bloc والان سكون الشرح باذن الله عن Riverpod.
عند زيارتك الصفحة الخاصة بريفربود ستجد هذه الرسالة "Provider, but different" اذا كنت لم تتعامل مع Provider من قبل فهو ببساطة مكتبة لادارة بيانات التطبيق تقوم بتعريف متغير "Global" يمكنك التواصل معه من اي مكان في التطبيق، لكن لا تخف من "Global" لانه سيكون "immutable". ولن يؤثر على اداء التطبيق باذن الله.
الموقع الخاص بالمكتبة يقدم لكن انواع عديده من الباكجز ولكن سنقوم باختيار Riverpod بدون Hook واذا كان لديك معرفه سابقة في Hook فالاختلاف باذن الله لن يكون كبير،.
تهيئة المشروع:
على افتراض انك انشئت مشروع جديد قم باضافة التالي الى pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^0.12.4
الان بعد تحمل المكتبة سنقوم باضافة التطبيق داخل ProviderScope
حتى نستيطع قراءة Provider وايضا هي المكان المخصص لحفظ البيانات. او حالة التطبيق.
void main() {
runApp(ProviderScope(child: MyApp()));
}
الان اصبح المشروع جاهز للتعامل مع Riverpod، سنقوم باستعراض جميع المفاهيم المتعلقة Riverpod كل واحدة على حدة. وهم بالترتيب.
- Provider
- State
- Future
Provider
Provider ياتي باشكال مختلفة ولكن سنقوم بالتعرف عل الشكل العام له، مع مثال بسيط يعرض نص اهلا بالعالم، بداية سنقوم بانشاء Provider كـGlobal وطريقة تعريفة مثل اي دالة.
final myProvider = Provider((ref) => "اهلا بالعالم");
myProvider
: متغيرfinal
، هذا المتغير الذي سنقوم بالتعامل معه لمعرفة الحالة لديه، مع ملاحظة ان هذا الملاحظ غير قابل للتغير.Provider
: ابسط نوع من انواعprovider
، يحمل قيمة لايمكن ان تتغير.- دالة تنشئ الحالة المشتركة : دائما هذه الدالة تحمل باريمتر
ref
الذي يسمح لنا بقراء حالة المزودين الاخرين، سنقوم بملاحظة هذه الوظيفة لاحقا في الدرس.
قراءة الـProvider :
سنقوم بانشاء Widget
ونقرأ البيانات من Provider
،
class HelloWorld extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
String helloWorld = watch<String>(myProvider);
return Scaffold(
appBar: AppBar(),
body: Center(child: Text(helloWorld)),
);
}
}
- تلاحظ ان
Widget
وريث منConsumerWidget
وهو يعتبرStatelessWidget
بحيث يسمح لنا بمراقيةProvider
وارجاع البيانات. - ايضا دالة
build
اضيف لها باريمترScopedReader
باسمwatch
يراقب الـProvider
ويقوم باعادة بناء الواجهة عند الحاجة. - بعد ذلك عرفنا متغير نصي
helloWorld
يستخدمwatch
وداخلةmyProvider
الذي قمنا بتعريفه سابقاً. - واخير وضعنا المتغير النصي في
Text
لعرضه على الشاشة.
الان تعلمنا مهوم Provider
لكن لو اردنا تغيير البيانات ?.
استخدام State Provider والجمع بين الـProviders:
في كثير من الحالات تحتاج لحعل Providers يعتمدون على قيمة بعض، مثلا لو كان لديك قائمة بالمدن وتريد عرض مثلا حالة الطقس للمدينة المختارة، فانت تحتاج الى Provider لعرض قائمة المدن مثلا وProvider آخر لجلب بيانات المدينة المختارة. في هذه الحالة سنقوم بعملية جمع للاثنين كما ستلاحظ الان.
في المثال التالي بانشاء زرين كل واحد يقوم بتوليد قيمة مختلفة والـProvider الاخر يحصل عليها ويعيد لنا قيمة أخرى.
final selectThemeProvider = StateProvider<String>((ref) {
return '';
});
final changeThemeProvider = Provider<bool>((ref) {
final selectTheme = ref.watch(selectThemeProvider);
return selectTheme.state == 'dark';
});
انشئنا StateProvider
حتى نقوم بتغيير الحالة له واضفنا له قيمة نصية فارغة، ثم قمنا بتعريف changeThemeProvider
الذي سيقوم بدورة بمراقية state
للـProvider السابق وبناء على الحالة سيعيد قيمة True
او false
. الان سنقوم بانشاء Widget
للتعامل معهم.
class SelectThemeWidget extends ConsumerWidget {
@override
// 1
Widget build(BuildContext context, ScopedReader watch) {
// 2
final bool isDark = watch<bool>(changeThemeProvider);
return Scaffold(
appBar: AppBar(
title: Text('Providers'),
),
body: Container(
// 3
color: isDark ? Colors.black : Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ElevatedButton(
onPressed: () {
// 4
context.read(selectThemeProvider).state = 'light';
},
child: Text("وضع النهار")),
ElevatedButton(
onPressed: () {
// 5
context.read(selectThemeProvider).state = 'dark';
},
child: Text("وضع الليل"))
],
)),
);
}
}
- دالة
build
اضفنا لهاScopedReader
لمتابعة الـProvider. - انشئنا متغير
bool
حتى نقوم بمعرفة المتغير اذا كان وضع الليلي او النهار. - بناء على قيمة المتغير نقوم بتغيير خلفية الـ
Container
اسود او ابيض. - استخدمنا
context
لقراءةprovider
وتغيير القيمة له. - نفس المذكور في النقطة لرابعة.
الان سنقوم بالتعامل مع المثال الشائع لادارة حالة التطبيق وهو Counter App
سنقوم باخذ طريقتين للتعامل معه ونوضح الفرق بين الطريقتين.
الفرق بين State Provider و Change Notifier Provider:
يعتبر من اهم المميزات لتغيير القيم للتطبيق، يوجد طريقتين لتغيير الحالة للتطبيق، ،سنقوم بشرحها جميعاً باستخدام المثال البسيط وهو مثال العداد، في الكود التالي ستلاحظ يوجد ترقيم للاكواد وستجد شرحها في بعد الكود مباشرة،
final counterProvider = StateProvider((ref) => 0);
تلاحظ انشأنا StateProvider
وهذا يعني ان القيمة ستقوم بالتغير ووضعنا القيمة المبدئية 0، بعد ذلك سنقوم بانشاء Widget
للتعامل مع الحالة وبنفس الطريقة سيكون الـWidget
وريث من ConsumerWidget
.
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
// 1
int counter = watch(counterProvider).state;
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
// 2
context.read(counterProvider).state++;
},
),
body: Center(child: Text("$counter")));
}
}
- مشاهدة الحالة من
counterProvider
. - تغيير قيمة الحالة باستخدام
++
.
لكن ماذا لو كان التطبيق كبير، هل ستقوم بالتعامل بنفس الطريقة؟ ? بالطبع لا ستقوم بالتأكيد بجعل تطبيقك اكثر قابلية للفهم والتطوير والصيانة وذلك عن طريق استخدام ChangeNotifier
سنقوم باستخام نفس الــprovider
ولكن مع بعض التغييرات، في البداية سنقوم بانشاء كائن ChangeNotifier
.
class CounterNotifier extends ChangeNotifier {
int count;
CounterNotifier([this.count = 0]);
void increment() {
count++;
notifyListeners();
}
}
الان سنقوم بتغيير على كود counterProvider
السابق كتابتها لتكون بهذ الشكل:
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
// 1
int counter = watch(counterProvider).count;
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
// 2
context.read(counterProvider).increment();
},
),
body: Center(child: Text("$counter")));
}
}
- قراءة قيمة
count
. - استخدمنا الدالة المضافة في
CounterNotifier
لزيادة الرقم.
هذا الكود يعطيك نفس نتيجة الكود السابق.
في الدرس القادم بإذن الله سنتعلم
- FutureProvider
- StateProvider
لا تنسى تقييم الدرس