Flutter Provider

Provider နှင့် ပတ်သက်ပြီး သိသင့်သမျှ အပိုင်း (၁)

ကျွန်တော်တို့ Flutter မှာ Widget State အတွက် StatefulWidget ကို အသုံးပြုလို့ရပါတယ်။ ဒါပေမယ့် App State အတွက်ဆိုရင်တော့ ရွေးချယ်စရာတွေ ရှိလာပါတယ်။ Redux သုံးမလား၊ BLoC သုံးမလား၊ ဒါမှမဟုတ် MobX သုံးမလား၊ Provider သုံးမလားပေါ့။ အားလုံးကို နှိုင်းယှဉ်ကြည့်မယ်ဆိုရင် သူ့နေရာနဲ့သူ အားသာချက်တွေ ရှိသလို အားနည်းချက်တွေလဲ ရှိနေပါတယ်။

ဒီအပိုင်းမှာတော့ Provider အကြောင်းကိုပဲ ပြောပြမှာ ဖြစ်ပါတယ်။ Provider ဟာ Inheritance Widget ကို ကျွန်တော်တို့ အသုံးပြုလို့ လွယ်ကူအောင် ပြုလုပ်ပေးထားတဲ့ Package ဖြစ်ပါတယ်။ Theme.of(context) စသည်ဖြင့် နဂိုကတည်းကလဲ သုံးနေရတာ ဖြစ်တဲ့အတွက် Provider ဟာ လူသုံးများလာပြီး လျှင်လျှင်မြန်မြန် ဖွံ့ဖြိုး တိုးတက်လာတယ်လို့ ဆိုနိုင်ပါတယ်။ အခု ကျွန်တော် ရေးတဲ့ အချိန်မှာ Provider ဟာ Version 4.x အထက်ကို ရောက်နေပြီ ဖြစ်ပါတယ်။

Provider ဆိုတာဘာလဲ

Provider ဆိုတာ Dependency Injection ကို အထောက်အကူ ပြုပေးတဲ့ Package တစ်ခု ဖြစ်ပါတယ်။ ဒါပေမယ့် Provider ဟာ DI အတွက် တစ်ခုတည်း မဟုတ်ပါဘူး။ Flutter ရဲ့ State Management အပိုင်းနဲ့ပါ တွဲပြီး ဆောင်ရွက်လို့ရတဲ့ Package ဖြစ်ပါတယ်။ နဂိုမူလ Flutter ရဲ့ ကျောရိုးတည်ဆောက်မှု အပေါ်မှာ တင်ပြီး တည်ဆောက်နိုင်တဲ့အတွက် Flutter နဲ့ ပိုပြီး အသားကျတယ်လို့ ဆိုနိုင်ပါတယ်။

Provider ကိုဘာကြောင့်သုံးသင့်တာလဲ

Provider ကို အောက်ပါ အချက်တွေကြောင့် သုံးသင့်ပါတယ်။

  1. Dependency Injection (DI)
  2. Lazy Loading
  3. State Management with Flutter
  4. Flutter Developers’ Preference
  5. Easy to implement

Dependency Injection ကတော့ အစိတ်အပိုင်း တစ်ခုချင်းစီမှာ ဆက်စပ်မှု ရှိနေတဲ့ Application တိုင်းမှာ လိုအပ်တဲ့ နည်းပညာ တစ်ခုပါ။ Laravel မှာဆိုရင်လဲ IoC Container လို DI Container တွေရှိပါတယ်။ Kotlin မှာဆိုရင်လဲ Kodein လိုဟာတွေ ရှိပါတယ်။ 

နောက်တစ်ချက်ကတော့ Lazy Loading ပါ။ Object တိုင်းဟာ DI Container ထဲကို ထည့်ထားပေမယ့် သုံးဖြစ်ချင်မှ သုံးဖြစ်တာမျိုးတွေရှိပါတယ်။ အဲဒီလို အခြေအနေတွေမှာ Lazy Loading ဟာ အရေးပါပါတယ်။ ဥပမာအနေနဲ့ ပြောရမယ်ဆိုရင် အိမ်ဆောက်မယ့် လက်သမားတစ်ယောက်ဟာ တူတွေ လွှတွေလို Static ဖြစ်တဲ့ ပစ္စည်း ပစ္စယတွေလဲ ယူလာဖို့ လိုသလို ရေပေါ်စက်လို လျှပ်စစ်မီးနဲ့ ချိတ်ပြီး သုံးရတဲ့ စက်ပစ္စည်းတွေလဲ လိုအပ်ပါတယ်။ Lazy Loading သာ မရဘူးဆိုရင် အဲဒီလို စက်တွေကို နိုးထားပြီးမှ DI ထဲထည့်ထားရပါလိမ့်မယ်။ Lazy Loading ဆိုတာက ထည့်တော့ယူလာတယ်။ ဒါပေမယ့် လိုအပ်မှ ပလပ်ထိုးပြီး သုံးမယ်ဆိုတဲ့ သဘောမျိုးပါ။ 

နောက်တစ်ခုကတော့ State Management ပါ။ Widget State တွေကို စီမံခန့်ခွဲတဲ့ နေရာမှာရော Application State ကို စီမံခန့်ခွဲတဲ့နေရာမှာပါ State Management က Flutter မှာ အလွန် အရေးပါပါတယ်။ State အကြောင်း မသိရင် Flutter မသိဘူးလို့တောင် ဆိုလို့ရပါတယ်။ Provider အနေနဲ့ Flutter ရဲ့ နဂိုမူလ Widiget State တွေဖြစ်တဲ့ Inheritance State တို့ StatefulWidget တို့မှာလဲ တစ်သားတည်း ပေါင်းစပ် အသုံးချနိုင်ပါတယ်။ 

နောက်တစ်ချက်က Flutter Developer တွေ အရင်က BLoC Pattern ကို ညွှန်းကြပေမယ့် နောက်ပိုင်းမှာတော့ Provider ကို ပိုပြီး ညွှန်းလာကြပါတယ်။ ဘာကြောင့် ညွှန်းရသလဲဆိုရင် အပေါ်မှာ ပြောထားတဲ့ အားသာချက်တွေကြောင့်ပါ။ 

နောက်ဆုံးတစ်ခုကတော့ အသုံးပြုရလွယ်တာပါ။ အဲ … လွယ်တယ်ဆိုပေမယ့် ခက်တာလေးတွေ ရှိနိုင်တဲ့အတွက် ဒီဆောင်းပါးကို ရေးလိုက်ရတာ ဖြစ်ပါတယ်။

In Provider

Provider မှာ အသုံးပြုတဲ့ အပေါ် မူတည်ပြီး အထောက်အပံ့ပေးထားတဲ့ စနစ်တွေရှိပါတယ်။ အဲဒါတွေကတော့ အောက်မှာ ပြထားတဲ့အတိုင်း ဖြစ်ပါတယ်။

  1. Provider
  2. Consumer
  3. ChangeNotifierProvider
  4. ListenableProvider
  5. ValueListenableProvider
  6. Future Provider
  7. StreamProvider
  8. ProxyProvider

ဒီအပိုင်းမှာတော့ တစ်ခုခြင်းစီအလိုက် နမူနာလေးတွေနဲ့ ရေးပြသွားပါမယ်။ Provider အတွက် နမူနာ Project ကိုတော့ GitHub မှာ တင်ထားပါတယ်။

Provider

Provider ကတော့ Parent Widget တွေမှာ DI အနေနဲ့ Inject လုပ်တဲ့အပိုင်း ဖြစ်ပါတယ်။ ကျွန်တော်ပေးထားတဲ့ နမူနာမှာဆိုရင် အောက်မှာ ပြထားတဲ့အပိုင်း ဖြစ်ပါတယ်။

return MultiProvider(
      providers: [
        Provider<SimpleModel>(
          create: (context) => SimpleModel(),
        ),
        FutureProvider<FutureObject>(
          create: (context) => FutureObject().create(),
        ),
        Provider<ApiService>(
          create: (_) => ApiService.create(),
        ),
        ChangeNotifierProvider<NotifyObject>(
          create: (context) => NotifyObject(),
        ),
        Provider<NetworkProvider>(
          create: (context) => NetworkProvider(),
          dispose: (context, service) => service.disposeStreams(),
        )
      ],

ကျွန်တော်ကတော့ အများကြီး Inject လုပ်ချင်တာ ဖြစ်တဲ့အတွက် Simple Object ရယ်၊ NotifyObject ရယ် စသည်ဖြင့် အစုံထည့်ထားပါတယ်။

Consumer

Consumer ကတော့ ဒီ Widget ထဲမှာတင်ပဲ Inject လဲလုပ်တယ် သုံးလဲ သုံးချင်တယ်ဆိုရင် Consumer နဲ့ ရေးလို့ရပါတယ်။ ကျွန်တော်ရေးထားတဲ့ နမူနာ main.dart မှာ အပေါ်မှာ Inject လုပ်ပြီး အောက်မှာ တစ်ခါတည်း တန်းသုံးထားတာ ရှိပါတယ်။ အောက်မှာ ပြထားတဲ့အတိုင်းပါ။

Consumer<SimpleModel>(
                  builder: (context, simpleModel, child)
                  {
                    print("SimpleModel Container Widget Built!");
                    return Container(
                      width: double.infinity,
                      height: 100.0,
                      child: Center(
                        child: Text("${simpleModel.appId}"),
                      ),
                  );
                }
              ),

ChangeNotifierProvider

ChangeNotifierProvider ကတော့ Widget State တွေနဲ့ တွဲသုံးချင်တဲ့အခါ သုံးပါတယ်။ တစ်ခုခု အပြောင်းအလဲရှိတဲ့ UI ကို Render ပြန်လုပ်အောင် notifyListeners() ဆိုပြီး ခေါ်ပေးဖို့လိုပါတယ်။ ကျွန်တော်ရေးထားတဲ့ နမူနာထဲမှာဆိုရင် အောက်မှ ပြထားတာက ChangeNotifier ကို Mixin အနေနဲ့ အသုံးပြုထားတဲ့ Object ပါ။

import 'package:flutter/material.dart';

class NotifyObject with ChangeNotifier {
  int count = 0;

  void increase() {
    count++;
    notifyListeners();
  }

}

အဲဒါကို Consumer အနေနဲ့ ပြန်သုံးထားပါတယ်။

Consumer<NotifyObject>(
                builder: (context, notifyObject, child) {
                  print("NotifyObject Container Widget Built!");
                  return Container(
                    width: double.infinity,
                    height: 100.0,
                    padding: EdgeInsets.only(top: 20.0),
                    child: Center(
                      child: Text(
                          "${notifyObject.count}",
                        style: Theme.of(context).textTheme.title,
                      ),
                    ),
                  );
                },
              ),

NotifyObject မှာရှိတဲ့ count ကို တိုးဖို့အတွက် သုံးတာကတော့ ActionButton ဆိုပြီး ခွဲထုတ်ထားပါတယ်။ ဘာကြောင့်ခွဲထုတ်သလဲဆိုရင် သူက UI ကို ချိန်းတာ မဟုတ်ပဲ NotifyOject ထဲမှာရှိတဲ့ count ကို တိုးဖို့သက်သက်အတွက်ပဲ သုံးတာကြောင့်ပါ။

class ActionButton extends StatelessWidget {
  const ActionButton({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final notifyObject = Provider.of<NotifyObject>(context, listen: false);
    print("Action Button Built");
    return FloatingActionButton.extended(
        onPressed: () {
          notifyObject.increase();
        },
        label: Text("Increase"),
        icon: Icon(Icons.add),
    );
  }
}

အဲဒီမှာ Provider.of<NotifyObject>(context, listen: false) ဆိုပြီး သုံးတာ သတိထားမိပါလိမ့်မယ်။ အဲဒီအပြင် print(“Action Button Built”) ဆိုပြီး print ထုတ်ထားပါတယ်။ အဲဒါကို Run ကြည့်မယ်ဆိုရင် ActionButton Widget ဟာ တစ်ကြိမ်ပဲ Render လုပ်သွားတာ သတိထားမိပါလိမ့်မယ်။ ဒါကတော့ Flutter ရေးမယ်ဆိုရင် ဘယ် Widget က Render လုပ်ဖို့ လိုလဲဆိုတာ သေသေချာချာ သိဖို့လိုတာကို နမူနာပြထားတာ ဖြစ်ပါတယ်။

ဆက်ပါဦးမယ်