Flutter狀態管理Provider(一) 簡易使用

mwq30123發表於2020-04-05

前言

如果對Provider的使用已經很熟練,可以跳過這部分內容,直接看過程分析部分 Flutter狀態管理Provider(二)過程分析

簡介

官網介紹

[Provider](https://github.com/rrousselGit/provider)

簡單翻譯來說,Provider:依賴注入與狀態管理的結合,並且對外提供Widget使用。使用widget代替純Dart物件,比如說Stream。因為widget簡單,強大且可擴充套件。使用Provider,可以保證:可維護性(強制的單向資料流),可測試性,健壯性等等。

A mixture between dependency injection (DI) and state management, built with widgets for widgets.It purposefully uses widgets for DI/state management instead of dart-only classes like Stream.

The reason is, widgets are very simple yet robust and scalable.

By using widgets for state management, provider can guarantee:
複製程式碼
  • maintainability, through a forced uni-directional data-flow
  • testability/composability, since it is always possible to mock/override a value
  • robustness, as it is harder to forget to handle the update scenario of a model/widget

什麼是狀態?什麼是狀態管理?

如果說頁面是靜態,拿到資料,渲染完就完事了.壓根就不需要狀態管理。 不幸的是,頁面不可能全是靜態。頁面需要響應資料變化(網路資料?使用者操作產生的資料?),更新UI。同時,資料變化的影響,不僅僅是元件內,還有可能是頁面內其他元件,甚至於應用內其他頁面

資料即為狀態。從資料變化到通知介面更新的過程,我們稱之為狀態管理 狀態管理要儘可能的把這個過程獨立出來,讓動態介面如同靜態頁面一般簡單。

有哪些狀態管理的方式/庫

- setState
- FutureBuilder/StreamBuilder/BLoc
- Provider/ScopedModel
- redux/Fish-redux
複製程式碼

簡易版Provider實現

有了狀態管理的介紹,我們可以參考Provider,通過手上現有的元件,實現一個簡易版的Provider。要用到的系統元件:

  • setState:StatefulWidget現成的,重新整理UI的辦法
  • InheritedWidget:一個基礎widget,讓widget樹具備從上而下傳遞資料的能力。同時資料變化可以引起依賴它的widget重新構建。
  • ChangeNotifier:觀察者模式的程式碼模型。

setState

狀態管理最基礎的一個實現

class _SetStateDemoWidgetState extends State<SetStateDemoWidget> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(setStateDemoTitle),),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text("計數:$count"),
            RaisedButton(
              child: Text("increment"),
              onPressed: () => setState(() => count++),
            )
          ],
        ),
      ),
    );
  }
}
複製程式碼

InheritedWidget

用CountProvider繼承InheritedWidget來儲存資料。 通過context.getElementForInheritedWidgetOfExactType拿到CountProvider中的資料。 這個context一定要注意,只能用CountProvider子widget的BuildContext。CountProvider的查詢是通過context往上的。 注意這個地方,我們只是單純的拿資料,還沒有用到InheritedWidget可以控制子widget重新構建的功能。

///首先我們要有個地方存放我們的資料
class CountProvider extends InheritedWidget {
  final int count;

  CountProvider({Key key, this.count, Widget child})
      : super(key: key, child: child);

  @override
  bool updateShouldNotify(CountProvider old) {
    return true;
  }
}

class ProviderDemoWidget1 extends StatefulWidget {
  ProviderDemoWidget1({Key key}) : super(key: key);

  @override
  _ProviderDemoWidget1State createState() => _ProviderDemoWidget1State();
}

class _ProviderDemoWidget1State extends State<ProviderDemoWidget1> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(providerDemoTitle1),
      ),
      body: CountProvider(
        count: _count,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Builder(
                builder: (context2) {
                  
                  CountProvider provider = context2
                      .getElementForInheritedWidgetOfExactType<CountProvider>()
                      .widget;
                  return Text("計數:${provider.count}");
                },
              ),

              /// 讀取和顯示計數
              RaisedButton(
                child: Text("increment"),
                onPressed: () => setState(() => _count++),
              ),
              Text(providerDemoIntroduction1),
            ],
          ),
        ),
      ),
    );
  }
}
複製程式碼

Provider(InheritedWidget + ChangeNotifier)

我們先看效果圖,我們看到有三種場景。只有依賴的元件更新了UI。

Flutter狀態管理Provider(一) 簡易使用

  • CountModel封裝示例1中的count,繼承ChangeNotifier,具備notify能力。
class CountModel extends ChangeNotifier {
  int count;

  CountModel(this.count);

  void increment() {
    count++;
    notifyListeners();
  }
}
複製程式碼
  • Provider繼承InheritedWidget,封裝兩種訪問: context.dependOnInheritedWidgetOfExactType和context.getElementForInheritedWidgetOfExactType. 當InheritedWidget重新構建是,前者widget會重新構建,後者則不會。
class Provider<T extends ChangeNotifier> extends InheritedWidget {
  final T model;

  Provider({Key key, this.model, Widget child}) : super(key: key, child: child);

  static T of<T extends ChangeNotifier>(BuildContext context, bool depend) {
    if (depend) {
      return context.dependOnInheritedWidgetOfExactType<Provider>().model;
    } else {
      Provider provider =
          context.getElementForInheritedWidgetOfExactType<Provider>().widget;
      return provider.model;
    }
  }

  @override
  bool updateShouldNotify(Provider old) {
    return true;
  }
}
複製程式碼
  • ChangeNotifierProvider通過監聽ChangeNotifier,將setState封裝在其內部。總之,把狀態管理(資料修改和Widget重新整理)封裝在自定義的物件內。外部控制元件不需要再關心狀態管理的細節了。
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
  final Widget child;

  final T model;

  ChangeNotifierProvider({this.child, this.model});

  @override
  _ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}

class _ChangeNotifierProviderState extends State<ChangeNotifierProvider> {
  _ChangeNotifierProviderState();

  _update() {
    setState(() => {});
  }

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

  @override
  void dispose() {
    super.dispose();
    widget.model.removeListener(_update);
  }

  @override
  Widget build(BuildContext context) {
    return Provider(
      model: widget.model,
      child: widget.child,
    );
  }
}
複製程式碼
  • 有了 CountModel,Provider,ChangeNotifierProvider,簡易版狀態管理Provider庫也就寫好了,下面用起來:
class ProviderDemoWidget3 extends StatefulWidget {
  ProviderDemoWidget3({Key key}) : super(key: key);
  
  @override
  _ProviderDemoWidget3State createState() => _ProviderDemoWidget3State();
}

class _ProviderDemoWidget3State extends State<ProviderDemoWidget3> {
  CountModel _countModel = CountModel(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(providerDemoTitle3), ),
      body: ChangeNotifierProvider<CountModel>(
        model: _countModel,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Builder(builder: (context1) {
                return Text(
                    "計數:${Provider.of<CountModel>(context1, true).count}(有依賴情況)");
              }),
              Builder(builder: (context2) {
                return Text(
                    "計數:${Provider.of<CountModel>(context2, false).count}(無依賴情況)");
              }),
              RaisedButton(
                  child: Text("increment"),
                  onPressed: () => _countModel.increment()),
              Text(providerDemoIntroduction3),
            ],
          ),
        ),
      ),
    );
  }
}
複製程式碼

基於上個版本的通用泛型封裝

上一個示例中CountModel不具有通用性,所以我們寫一個泛型版本

class Provider<T extends ChangeNotifier> extends InheritedWidget {
  final T model;

  Provider({Key key, this.model, Widget child}) : super(key: key, child: child);

  static T of<T extends ChangeNotifier>(BuildContext context, bool depend) {
    if (depend) {
      return context.dependOnInheritedWidgetOfExactType<Provider>().model;
    } else {
      Provider provider =
          context.getElementForInheritedWidgetOfExactType<Provider>().widget;
      return provider.model;
    }
  }

  @override
  bool updateShouldNotify(Provider old) {
    return true;
  }
}
複製程式碼
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
  final Widget child;

  final T model;

  ChangeNotifierProvider({this.child, this.model});

  @override
  _ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}

class _ChangeNotifierProviderState extends State<ChangeNotifierProvider> {
  _ChangeNotifierProviderState();

  _update() {
    setState(() => {});
  }

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

  @override
  void dispose() {
    super.dispose();
    widget.model.removeListener(_update);
  }

  @override
  Widget build(BuildContext context) {
    return Provider(
      model: widget.model,
      child: widget.child,
    );
  }
}
複製程式碼
class CountModel extends ChangeNotifier {
  int count;

  CountModel(this.count);

  void increment() {
    count++;
    notifyListeners();
  }
}
class ProviderDemoWidget3 extends StatefulWidget {
  ProviderDemoWidget3({Key key}) : super(key: key);

  @override
  _ProviderDemoWidget3State createState() => _ProviderDemoWidget3State();
}

class _ProviderDemoWidget3State extends State<ProviderDemoWidget3> {
  CountModel _countModel = CountModel(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(providerDemoTitle3),
      ),
      body: ChangeNotifierProvider<CountModel>(
        model: _countModel,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Builder(builder: (context1) {
                return Text(
                    "計數:${Provider.of<CountModel>(context1, true).count}(有依賴情況)");
              }),
              Builder(builder: (context2) {
                return Text(
                    "計數:${Provider.of<CountModel>(context2, false).count}(無依賴情況)");
              }),
              RaisedButton(
                  child: Text("increment"),
                  onPressed: () => _countModel.increment()),
              Text(providerDemoIntroduction3),
            ],
          ),
        ),
      ),
    );
  }
}
複製程式碼

真正Provider的用法

我們發現與上一個示例的簡易版Provider上的使用方法是一致的。 正如介紹所描述的非常簡單。當然了Provider庫,可維護性,可測試性,可擴充套件性遠比我們所寫的強大。

class ProviderDemoWidget4 extends StatefulWidget {
  ProviderDemoWidget4({Key key}) : super(key: key);

  @override
  _ProviderDemoWidget4State createState() => _ProviderDemoWidget4State();
}

class _ProviderDemoWidget4State extends State<ProviderDemoWidget4> {
  CountModel _countModel = CountModel(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(providerDemoTitle4),
      ),
      body: ChangeNotifierProvider.value(
        value: _countModel,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Consumer<CountModel>(
                builder: (contextC, model, child) {
                  return Text("計數:${model.count}(有依賴情況)");
                },
              ),
              Builder(builder: (context2) {
                return Text(
                    "計數:${Provider.of<CountModel>(context2, listen: false).count}(無依賴情況)");
              }),
              RaisedButton(
                  child: Text("increment"),
                  onPressed: () => _countModel.increment()),
              Text(providerDemoIntroduction4),
            ],
          ),
        ),
      ),
    );
  }
}

class CountModel extends ChangeNotifier {
  int count;

  CountModel(this.count);

  void increment() {
    count++;
    notifyListeners();
  }
}
複製程式碼

到此為止,我們也就瞭解了Provider的基本原理和基本使用。 可我們的征程才開始。知其然,更要知其所以然。接下來,我們從setState開始一步步分析Provider狀態管理的過程。

Demo地址

文章中用到的Demo程式碼

入口:main_provider.dart

參考資料

Flutter實戰

相關文章