Flutter狀態管理provider的使用和封裝

Flutter狀態管理provider的使用和封裝

Flutter提供了InheritedWidget類,幫助咱們處理父子組件之間的狀態管理。provider是InheritedWidget的封裝,讓開發者易於使用和服用。可是初看provider的文檔,有點讓人頭大:api

name description
Provider The most basic form of provider. It takes a value and exposes it, whatever the value is.
ListenableProvider A specific provider for Listenable object. ListenableProvider will listen to the object and ask widgets which depend on it to rebuild whenever the listener is called.
ChangeNotifierProvider A specification of ListenableProvider for ChangeNotifier. It will automatically call ChangeNotifier.dispose when needed.
ValueListenableProvider Listen to a ValueListenable and only expose ValueListenable.value.
StreamProvider Listen to a Stream and expose the latest value emitted.
FutureProvider Takes a Future and updates dependents when the future completes.

不是說provider是易於使用嗎?我只想以一種的簡單的方式管理狀態,卻給我這麼多選擇,到底我該選擇哪一個呢?選擇困難症急的想薅頭髮。bash

使用

新建Futter項目,更改默認的計數器佈局,效果以下: 服務器

Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-29 at 00.07.34.png

點擊FlatButton,更改應用程序的計數器狀態,使計數器加1,前兩行的text顯示計數器狀態最新值,FlatButton和兩個text是不一樣部分的widget。app

  1. 在的pubspec.yaml文件中依賴provider:
dependencies:
  flutter:
    sdk: flutter
  provider: ^4.1.2
複製代碼
  1. 導入: import 'package:provider/provider.dart';

Provider

Provider是provider包中最基本的提供者widget類型。它能夠給包括住的全部widget提供值,可是當該值改變時,並不會更新widget。less

新增MyModel類,做爲要讓Provider提供出去的值,把計數器的數值counter聲明到這裏,而且更改計數值的方法也放在這裏,點擊按鈕的時候,調用MyModel對象的incrementCounter(),延時2秒並更改counter:async

class MyModel {
  
  MyModel({this.counter=0});

  int counter = 0;

  Future<void> incrementCounter() async {
    await Future.delayed(Duration(seconds: 2));
    counter++;
    print(counter);
  }
}
複製代碼

在widget樹的頂部包裹Provider小部件,將MyModel對象經過Provider提供給widget樹。而後使用了兩種獲取Provider提供值的方式,在Column裏:ide

  1. 先使用Provider.of(context)獲取到MyModel對象的引用;
  2. 而後使用Consumer小部件得到對MyModel對象的引用;
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider(
      create: (_) => MyModel(),
      child: Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            Builder(
              builder: (context) {
                // 獲取到provider提供出來的值
                MyModel _model = Provider.of<MyModel>(context);
                return Container(
                    margin: const EdgeInsets.only(top: 20),
                    width: MediaQuery.of(context).size.width,
                    padding: const EdgeInsets.all(20),
                    alignment: Alignment.center,
                    color: Colors.lightBlueAccent,
                    child: Text('當前是:${_model.counter}'));
              },
            ),
            Consumer<MyModel>(
                // 獲取到provider提供出來的值
              builder: (context, model, child) {
                return Container(
                  margin: const EdgeInsets.only(top: 20),
                  width: MediaQuery.of(context).size.width,
                  padding: const EdgeInsets.all(20),
                  alignment: Alignment.center,
                  color: Colors.lightGreen,
                  child: Text(
                    '${model.counter}',
                  ),
                );
              },
            ),
            Consumer<MyModel>(
               // 獲取到provider提供出來的值
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    onPressed:model.incrementCounter,
                    child: Icon(Icons.add));
              },
            ),
          ],
        ),
      ),
    );
  }
}

複製代碼

點擊FlatButton,model調用incrementCounter()函數,計數值加1。可是並不會重建UI,由於該Provider小部件不會監聽其提供的值的更改。 函數

Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-29 at 00.07.34.png

打印出計數值的變化 佈局

Snipaste_2020-05-28_23-40-17.png

ChangeNotifierProvider

與最基礎的Provider小部件不一樣,ChangeNotifierProvider會監聽其提供出去的模型對象中的更改。當有值更改後,它將重建下方全部的Consumer和使用Provider.of(context)監聽並獲取提供值的地方。ui

代碼中更改Provider爲ChangeNotifierProvider。MyModel混入ChangeNotifier(繼承也同樣)。而後更改counter以後調用notifyListeners(),這樣ChangeNotifierProvider就會獲得通知,而且Consumer和監聽的地方將重建其小部件。

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => MyModel(),
      child: Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            Builder(
              builder: (context) {
                MyModel _model = Provider.of<MyModel>(context);
                return Container(
                    margin: const EdgeInsets.only(top: 20),
                    width: MediaQuery.of(context).size.width,
                    padding: const EdgeInsets.all(20),
                    alignment: Alignment.center,
                    color: Colors.lightBlueAccent,
                    child: Text('當前是:${_model.counter}'));
              },
            ),
            Consumer<MyModel>(
              builder: (context, model, child) {
                return Container(
                  margin: const EdgeInsets.only(top: 20),
                  width: MediaQuery.of(context).size.width,
                  padding: const EdgeInsets.all(20),
                  alignment: Alignment.center,
                  color: Colors.lightGreen,
                  child: Text(
                    '${model.counter}',
                  ),
                );
              },
            ),
            Consumer<MyModel>(
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    onPressed: model.incrementCounter,
                    child: Icon(Icons.add));
              },
            ),
          ],
        ),
      ),
    );
  }
}

class MyModel with ChangeNotifier{
  //                                               <--- MyModel
  MyModel({this.counter = 0});

  int counter = 0;

  Future<void> incrementCounter() async {
    await Future.delayed(Duration(seconds: 2));
    counter++;
    print(counter);
    notifyListeners();
  }
}
複製代碼

Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-29 at 00.21.22.png

每次點擊,都會更改計數器的值,若是第一行的計數值是保留初始值,不更新呢?很簡單,把Provider.of的監聽器設置爲false,這樣更改後就不會從新構建第一行的text: MyModel _model = Provider.of<MyModel>(context,listen: false);

Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-29 at 00.25.13.png

FutureProvider

FutureProvider基本上只是普通FutureBuilder的包裝。咱們須要給它提供一些顯示在UI中的初始數據,還要爲它設置要提供值的Future。在Future完成的時候,FutureProvider會通知Consumer重建本身的小部件。

在下面的代碼中,使用了一個counter爲0的MyModel向UI提供一些初始數據,而且添加了一個Future函數,可在3秒後返回一個counter爲1的MyModel。 和基類Provider同樣,FutureProvider它不會監聽模型自己內的任何更改。在下面的代碼中依舊經過按鈕點擊事件使counter加1,可是對UI沒有影響。

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureProvider(
      initialData: MyModel(counter: 0),
      create: (context) => someAsyncFunctionToGetMyModel(),
      child: Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            Builder(
              builder: (context) {
                MyModel _model = Provider.of<MyModel>(context, listen: false);
                return Container(
                    margin: const EdgeInsets.only(top: 20),
                    width: MediaQuery.of(context).size.width,
                    padding: const EdgeInsets.all(20),
                    alignment: Alignment.center,
                    color: Colors.lightBlueAccent,
                    child: Text('當前是:${_model.counter}'));
              },
            ),
            Consumer<MyModel>(
              builder: (context, model, child) {
                return Container(
                  margin: const EdgeInsets.only(top: 20),
                  width: MediaQuery.of(context).size.width,
                  padding: const EdgeInsets.all(20),
                  alignment: Alignment.center,
                  color: Colors.lightGreen,
                  child: Text(
                    '${model.counter}',
                  ),
                );
              },
            ),
            Consumer<MyModel>(
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    onPressed: model.incrementCounter,
                    child: Icon(Icons.add));
              },
            ),
          ],
        ),
      ),
    );
  }

  Future<MyModel> someAsyncFunctionToGetMyModel() async {
    //  <--- async function
    await Future.delayed(Duration(seconds: 3));
    return MyModel(counter: 1);
  }
}

class MyModel with ChangeNotifier {
  //                                               <--- MyModel
  MyModel({this.counter = 0});

  int counter = 0;

  Future<void> incrementCounter() async {
    await Future.delayed(Duration(seconds: 2));
    counter++;
    print(counter);
    notifyListeners();
  }
}
複製代碼

FutureProvider經過設置的Future完成後會通知Consumer,從新build。可是,Future完成後,點擊按鈕也不會更新UI。

FutureProvider適用於沒有刷新和變動的頁面,和FutureBuilder同樣的做用。

StreamProvider

StreamProvider基本上是StreamBuilder的包裝,和上面的FutureProvider同樣。不一樣的是StreamProvider提供的是流,FutureProvider須要的一個Future。

StreamProvider也不會監聽model自己的變化。它僅監聽流中的新事件:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamProvider(
      initialData: MyModel(counter: 0),
      create: (context) => getStreamOfMyModel(),
      child: Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            Builder(
              builder: (context) {
                MyModel _model = Provider.of<MyModel>(context, listen: false);
                return Container(
                    margin: const EdgeInsets.only(top: 20),
                    width: MediaQuery.of(context).size.width,
                    padding: const EdgeInsets.all(20),
                    alignment: Alignment.center,
                    color: Colors.lightBlueAccent,
                    child: Text('當前是:${_model.counter}'));
              },
            ),
            Consumer<MyModel>(
              builder: (context, model, child) {
                return Container(
                  margin: const EdgeInsets.only(top: 20),
                  width: MediaQuery.of(context).size.width,
                  padding: const EdgeInsets.all(20),
                  alignment: Alignment.center,
                  color: Colors.lightGreen,
                  child: Text(
                    '${model.counter}',
                  ),
                );
              },
            ),
            Consumer<MyModel>(
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    onPressed: model.incrementCounter,
                    child: Icon(Icons.add));
              },
            ),
          ],
        ),
      ),
    );
  }

  Stream<MyModel> getStreamOfMyModel() {
    return Stream<MyModel>.periodic(
        Duration(seconds: 1), (x) => MyModel(counter: x)).take(10);
  }
}

class MyModel with ChangeNotifier {
  //                                               <--- MyModel
  MyModel({this.counter = 0});

  int counter = 0;

  Future<void> incrementCounter() async {
    await Future.delayed(Duration(seconds: 2));
    counter++;
    print(counter);
    notifyListeners();
  }
}
複製代碼

給StreamProvider設置了一個每隔1秒更新一次的stream,ui上的計數值也是每隔一秒改變一次。可是點擊按鈕一樣不會刷新ui。因此也能夠認爲是一個StreamBuilder。

ValueListenableProvider

ValueListenableProvider相似於ValueChange的封裝,它的做用和ChangeNotifierProvider同樣,在值改變的時候,會通知Consumer從新build,可是使用起來比ChangeNotifierProvider複雜,須要先用Provider提供MyModel給Consumer,而後把MyModel裏的ValueNotifier給ValueListenableProvider:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider<MyModel>(
      create: (context) => MyModel(),
      child: Consumer<MyModel>(
        builder: (context, myModel, child) {
          return ValueListenableProvider<int>.value(
            value: myModel.counter,
            child: Scaffold(
              appBar: AppBar(
                title: Text('provider'),
              ),
              body: Column(
                children: <Widget>[
                  Builder(
                    builder: (context) {
                      var count = Provider.of<int>(context);
                      return Container(
                          margin: const EdgeInsets.only(top: 20),
                          width: MediaQuery.of(context).size.width,
                          padding: const EdgeInsets.all(20),
                          alignment: Alignment.center,
                          color: Colors.lightBlueAccent,
                          child: Text('當前是:$count'));
                    },
                  ),
                  Consumer<int>(
                    builder: (context, value, child) {
                      return Container(
                        margin: const EdgeInsets.only(top: 20),
                        width: MediaQuery.of(context).size.width,
                        padding: const EdgeInsets.all(20),
                        alignment: Alignment.center,
                        color: Colors.lightGreen,
                        child: Text(
                          '$value',
                        ),
                      );
                    },
                  ),
                  Consumer<MyModel>(
                    builder: (context, model, child) {
                      return FlatButton(
                          color: Colors.tealAccent,
                          onPressed: model.incrementCounter,
                          child: Icon(Icons.add));
                    },
                  ),
                ],
              ),
            ),
          );
        }
      ),
    );
  }
}

class MyModel {
  ValueNotifier<int> counter = ValueNotifier(0);
  Future<void> incrementCounter() async {
    await Future.delayed(Duration(seconds: 2));
    print(counter.value++);
    counter.value = counter.value;
  }
}
複製代碼

ListenableProvider

ListenableProvider和ChangeNotifierProvider同樣, 區別在於,若是Model是一個複雜模型ChangeNotifierProvider 會在你須要的時候,自動調用其 _disposer 方法,因此通常仍是使用ChangeNotifierProvider便可。

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListenableProvider<MyModel>(
      create: (context) => MyModel(),
      child: Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            Builder(
              builder: (context) {
                MyModel modol = Provider.of<MyModel>(context);
                return Container(
                    margin: const EdgeInsets.only(top: 20),
                    width: MediaQuery.of(context).size.width,
                    padding: const EdgeInsets.all(20),
                    alignment: Alignment.center,
                    color: Colors.lightBlueAccent,
                    child: Text('當前是:${modol.counter}'));
              },
            ),
            Consumer<MyModel>(
              builder: (context, model, child) {
                return Container(
                  margin: const EdgeInsets.only(top: 20),
                  width: MediaQuery.of(context).size.width,
                  padding: const EdgeInsets.all(20),
                  alignment: Alignment.center,
                  color: Colors.lightGreen,
                  child: Text(
                    '${model.counter}',
                  ),
                );
              },
            ),
            Consumer<MyModel>(
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    onPressed: model.incrementCounter,
                    child: Icon(Icons.add));
              },
            ),
          ],
        ),
      ),
    );
  }
}

class MyModel with ChangeNotifier {
  int counter = 0;

  Future<void> incrementCounter() async {
    await Future.delayed(Duration(seconds: 2));
    counter++;
    notifyListeners();
    print(counter);
  }
}
複製代碼

MultiProvider

上面的示例都僅使用了一個Model對象。若是須要提供第二種類型的Model對象,能夠嵌套Provider。可是,嵌套迷之縮進,可讀性低。這時候使用MultiProvider很是簡潔,

咱們改下上面的計數器,通常首頁會有一個banner和列表。咱們用上面的計數器模擬banner,下面的計數器模擬列表:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<BannerModel>(create: (context) => BannerModel()),
        ChangeNotifierProvider<ListModel>(create: (context) => ListModel()),
      ],
      child: Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            Builder(
              builder: (context) {
                BannerModel modol = Provider.of<BannerModel>(context);
                return Container(
                    margin: const EdgeInsets.only(top: 20),
                    width: MediaQuery.of(context).size.width,
                    padding: const EdgeInsets.all(20),
                    alignment: Alignment.center,
                    color: Colors.lightBlueAccent,
                    child: Text('當前Banner有幾個:${modol.counter}'));
              },
            ),
            Consumer<ListModel>(
              builder: (context, model, child) {
                return Container(
                  margin: const EdgeInsets.only(top: 20),
                  width: MediaQuery.of(context).size.width,
                  padding: const EdgeInsets.all(20),
                  alignment: Alignment.center,
                  color: Colors.lightGreen,
                  child: Text(
                    '當前Banner有幾個:${model.counter}',
                  ),
                );
              },
            ),
            Consumer<BannerModel>(
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    onPressed: model.getBanner,
                    child: Text("獲取banner"));
              },
            ),
            Consumer<ListModel>(
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    onPressed: model.getList,
                    child: Text("獲取列表"));
              },
            ),
          ],
        ),
      ),
    );
  }
}

class BannerModel with ChangeNotifier {
  int counter = 0;

  Future<void> getBanner() async {
    await Future.delayed(Duration(seconds: 2));
    counter++;
    notifyListeners();
    print(counter);
  }
}

class ListModel with ChangeNotifier {
  int counter = 0;

  Future<void> getList() async {
    await Future.delayed(Duration(seconds: 2));
    counter++;
    notifyListeners();
    print(counter);
  }
}
複製代碼

MultiProvider

按下banner按鈕,就單獨獲取banner的數值,並更新banner的Consumer。列表的同理。

ProxyProvider

若是要提供兩個Model,可是其中一個Model取決於另外一個Model,在這種狀況下,可使用ProxyProvider。A ProxyProvider從一個Provider獲取值,而後將其注入另外一個Provider,

把上面的改下,好比的上傳圖片功能,須要先把圖片提交到圖片服務器,而後再把連接發送到後臺服務器:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<PicModel>(create: (context) => PicModel()),
        ProxyProvider<PicModel, SubmitModel>(
          update: (context, myModel, anotherModel) => SubmitModel(myModel),
        ),
      ],
      child: Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            Builder(
              builder: (context) {
                PicModel modol = Provider.of<PicModel>(context);
                return Container(
                    margin: const EdgeInsets.only(top: 20),
                    width: MediaQuery.of(context).size.width,
                    padding: const EdgeInsets.all(20),
                    alignment: Alignment.center,
                    color: Colors.lightBlueAccent,
                    child: Text('提交圖片:${modol.counter}'));
              },
            ),

            Consumer<PicModel>(
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    onPressed: model.upLoadPic,
                    child: Text("提交圖片"));
              },
            ),
            Consumer<SubmitModel>(
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    onPressed: model.subMit,
                    child: Text("提交"));
              },
            ),
          ],
        ),
      ),
    );
  }
}

class PicModel with ChangeNotifier {
  int counter = 0;

  Future<void> upLoadPic() async {
    await Future.delayed(Duration(seconds: 2));
    counter++;
    notifyListeners();
    print(counter);
  }
}

class SubmitModel {
  PicModel _model;

  SubmitModel(this._model);

  Future<void> subMit() async {
    await _model.upLoadPic();
  }
}
複製代碼

基於MVVM模式封裝Provider

相信你們都已經理解provider的流程,以下圖:

MVVM

上面已經演示完了Provider的用法,在開發中,咱們須要Model充當ViewModel,處理業務邏輯,可是每次都寫樣板代碼的話也很麻煩,因此須要封裝下,易於使用。

Simulator Screen Shot - iPhone SE (2nd generation) - 2020-05-31 at 23.35.55.png

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<LoginViewModel>(
      create: (BuildContext context) {
        return LoginViewModel(loginServive: LoginServive());
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            Consumer<LoginViewModel>(
              builder: (context, model, child) {
                return Text(model.info);
              },
            ),
            Consumer<LoginViewModel>(
              builder: (context, model, child) {
                return FlatButton(
                    color: Colors.tealAccent,
                    onPressed: () => model.login("pwd"),
                    child: Text("登陸"));
              },
            ),
          ],
        ),
      ),
    );
  }
}

/// viewModel
class LoginViewModel extends ChangeNotifier {
  LoginServive _loginServive;
  String info = '請登陸';

  LoginViewModel({@required LoginServive loginServive})
      : _loginServive = loginServive;

  Future<String> login(String pwd) async {
    info = await _loginServive.login(pwd);
    notifyListeners();
  }
}

/// api
class LoginServive {
  static const String Login_path = 'xxxxxx';

  Future<String> login(String pwd) async {
    return new Future.delayed(const Duration(seconds: 1), () => "登陸成功");
  }
}
複製代碼

這種頁面寫法,基本每一個頁面都要,下面咱們一步一步開始封裝。

  1. 通常頁面載入的時候會顯示一個loading,而後加載成功展現數據,失敗就展現失敗頁面,因此枚舉一個頁面狀態:
enum ViewState { Loading, Success,Failure }
複製代碼
  1. ViewModel都會在頁面狀態屬性改變後更新ui,一般會調用notifyListeners,把這一步移到BaseModel中:
class BaseModel extends ChangeNotifier {
  ViewState _state = ViewState.Loading;

  ViewState get state => _state;

  void setState(ViewState viewState) {
    _state = viewState;
    notifyListeners();
  }
}
複製代碼
  1. 咱們知道ui裏須要ChangeNotifierProvider提供Model,而且用Consumer更新ui。所以咱們也將其內置到BaseView中:
class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
  final Widget Function(BuildContext context, T value, Widget child) builder;
  final T model;
  final Widget child;

  BaseWidget({Key key, this.model, this.builder, this.child}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _BaseWidgetState();

}

class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {

  T model;

  @override
  void initState() {
    model = widget.model;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<T>.value(
      value: model,
      child: Consumer<T>(
        builder: widget.builder,
        child: widget.child,
      ),
    );
  }
}
複製代碼
  1. 有時候咱們的頁面數據只是局部更新,Consumer的child屬性就是模型更改時不須要重建的UI,因此咱們將須要更新的ui放在builder裏,不須要更新的寫在child裏:
Consumer<LoginViewModel>(
  // Pass the login header as a prebuilt-static child
  child: LoginHeader(controller: _controller),
  builder: (context, model, child) => Scaffold(
    ...
    body: Column (

      children: [
//不更新的部分
        child,
        ...
      ]
    )
複製代碼
  1. 大多時候,咱們已進入一個頁面,就要獲取數據,因此咱們也把這個操做移入基類:
class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {

final Function(T) onModelReady;
...

 BaseWidget({
   ...
    this.onModelReady,
  });
  ...
}

...

@override
void initState() {
  model = widget.model;

  if (widget.onModelReady != null) {
    widget.onModelReady(model);
  }

  super.initState();
}
複製代碼

如今,咱們用封裝的基類完成登陸頁面:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BaseWidget<LoginViewModel>(
      model: LoginViewModel(loginServive: LoginServive()),
      builder: (context, model, child) => Scaffold(
        appBar: AppBar(
          title: Text('provider'),
        ),
        body: Column(
          children: <Widget>[
            model.state == ViewState.Loading
                ? Center(
                    child: CircularProgressIndicator(),
                  )
                : Text(model.info),
            FlatButton(
                color: Colors.tealAccent,
                onPressed: () => model.login("pwd"),
                child: Text("登陸")),
          ],
        ),
      ),
    );
  }
}

/// viewModel
class LoginViewModel extends BaseModel {
  LoginServive _loginServive;
  String info = '請登陸';

  LoginViewModel({@required LoginServive loginServive})
      : _loginServive = loginServive;

  Future<String> login(String pwd) async {
    setState(ViewState.Loading);
    info = await _loginServive.login(pwd);
    setState(ViewState.Success);
  }
}

/// api
class LoginServive {
  static const String Login_path = 'xxxxxx';

  Future<String> login(String pwd) async {
    return new Future.delayed(const Duration(seconds: 1), () => "登陸成功");
  }
}

enum ViewState { Loading, Success, Failure, None }

class BaseModel extends ChangeNotifier {
  ViewState _state = ViewState.None;

  ViewState get state => _state;

  void setState(ViewState viewState) {
    _state = viewState;
    notifyListeners();
  }
}

class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
  final Widget Function(BuildContext context, T model, Widget child) builder;
  final T model;
  final Widget child;
  final Function(T) onModelReady;

  BaseWidget({
    Key key,
    this.builder,
    this.model,
    this.child,
    this.onModelReady,
  }) : super(key: key);

  _BaseWidgetState<T> createState() => _BaseWidgetState<T>();
}

class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {
  T model;

  @override
  void initState() {
    model = widget.model;

    if (widget.onModelReady != null) {
      widget.onModelReady(model);
    }

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<T>(
      create: (BuildContext context) => model,
      child: Consumer<T>(
        builder: widget.builder,
        child: widget.child,
      ),
    );
  }
}
複製代碼
相關文章
相關標籤/搜索