شرح Flutter Riverpod لادارة State للتطبيق

في هذا الدرس سنتعلم 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("وضع الليل"))
            ],
          )),
    );
  }
}
  1. دالة build اضفنا لها ScopedReader لمتابعة الـProvider.
  2. انشئنا متغير bool حتى نقوم بمعرفة المتغير اذا كان وضع الليلي او النهار.
  3. بناء على قيمة المتغير نقوم بتغيير خلفية الـContainer اسود او ابيض.
  4. استخدمنا context لقراءة provider وتغيير القيمة له.
  5. نفس المذكور في النقطة لرابعة.

الان سنقوم بالتعامل مع المثال الشائع لادارة حالة التطبيق وهو 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")));
  }
}
  1. مشاهدة الحالة من counterProvider.
  2. تغيير قيمة الحالة باستخدام ++.
تلاحظ تغير القيمة عن التفاعل من الـFAB

لكن ماذا لو كان التطبيق كبير، هل ستقوم بالتعامل بنفس الطريقة؟ ? بالطبع لا ستقوم بالتأكيد بجعل تطبيقك اكثر قابلية للفهم والتطوير والصيانة وذلك عن طريق استخدام 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")));
  }
}
  1. قراءة قيمة count.
  2. استخدمنا الدالة المضافة في CounterNotifier لزيادة الرقم.
    هذا الكود يعطيك نفس نتيجة الكود السابق.

في الدرس القادم بإذن الله سنتعلم

  • FutureProvider
  • StateProvider

لا تنسى تقييم الدرس

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *