自己寫一個Provider

o動感超人o發表於2020-06-11

在以前我寫過一篇文章,教你如何實現FlutterBLoC框架(juejin.im/post/5cb80c… ),這個BLoC的實現,模仿android開發中的MVVM開發方式,利用FlutterStream,在資料改變的時候,由Stream推送資料給UI層,然後UI層自動更新UI。這次我們來自己實現一個Provider,這個也是Flutter中最常用的狀態管理框架,由我們自己實現Provider,來了解Provider內部核心原理。

首先,在上一篇文章中(InheritWidget原理解析:juejin.im/post/5edb97… ),我們檢視輸出日誌發現一個問題,就是點選“數字+1”這個按鈕的時候,“數字+1”這個按鈕也重新整理了(日誌輸出CustomRaisedButton build),這是因為我們呼叫的setState方法是BodyWidgetState這個類的方法,所以BodyWidget和它的child widget都重建了,而我們的需求是InheritedWidget的資料改變的時候只重新整理依賴此InheritedWidgetwidget,要做到這一點,我們就不能呼叫BodyWidgetState的setState方法,而只呼叫InheritedWidget上一個節點的setState,也就是說要把InheritedWidget作為一個StatefulWidgetchild。然後我們分步驟編寫程式碼:

  1. 建立可被監聽的資料管理類,為什麼我們不直接讓InheritedWidget成為可被監聽的型別呢?這是因為InheritedWidget只是提供資料,資料的消費者應該持有的是資料而不是InheritedWidget,否則資料消費者如果持有InheritedWidget的話,修改具體資料的方法就要新增到InheritedWidget裡,而資料型別是多種多樣的,不可能全部寫到InheritedWidget裡,所以我們要建立可被監聽的資料管理類,這樣當資料發生變化的時候,呼叫資料管理類的方法,資料管理類再通知該類的監聽者
  2. 建立InheritedWidget子類,用來儲存資料管理類,該類可以在重建以後通知所有依賴了該類的widget
  3. 建立StatefulWidget,這裡我們起名為ProviderCreator,用來儲存實際顯示的Widget和可被監聽的資料管理類,並根據兩者建立InheritedWidget,並監聽資料管理類的變化。這樣可以在資料管理類中的資料發生變化的時候可以通過ProviderCreator對應的State呼叫setState來讓InheritedWidget重建。InheritedWidget重建時如果ProviderCreator對應的Element沒有被銷燬的話,那這個ProviderCreator內部的可被監聽的資料管理類和實際顯示的child就被快取起來了(注意:這個child是我們傳入的實際顯示的widget,而不是ProviderCreator對應的Statebuild方法裡返回的widget

開始我們的程式碼編寫

1. 建立可被監聽的資料管理類

建立InheritedWidget子類的時候,裡面的資料可以是任意型別,但是我們需要在資料改變的時候通知監聽者,所以我們約束一下里面的資料型別必須是可被監聽的型別,在flutter裡,有一個類叫做ChangeNotifier,非常適合用來作為被監聽者,程式碼如下

class ChangeNotifier implements Listenable {
  ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();

  @protected
  bool get hasListeners {
    return _listeners.isNotEmpty;
  }
  @override
  void addListener(VoidCallback listener) {
    _listeners.add(listener);
  }

  @override
  void removeListener(VoidCallback listener) {
    _listeners.remove(listener);
  }

  @mustCallSuper
  void dispose() {
    _listeners = null;
  }

  @protected
  @visibleForTesting
  void notifyListeners() {
    if (_listeners != null) {
      final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
      for (final VoidCallback listener in localListeners) {
        listener();
      }
    }
  }
}
複製程式碼

程式碼我簡化了一下,就是上面的樣子,我們的可被監聽的資料類都需要繼承該類

2. 建立InheritedWidget子類

因為該類用來提供可被監聽的資料管理類,所以起名叫`Provider`
複製程式碼
class Provider<T extends ChangeNotifier> extends InheritedWidget {
  final T data;

  Provider({Key key, this.data, Widget child}) : super(key: key, child: child) {
    print("Provider=$hashCode");
  }

  //定義一個便捷方法,方便子樹中的widget獲取共享資料
  static Provider<T> of<T extends ChangeNotifier>(BuildContext context, bool dependOn) {
    if (dependOn) {
      return context.dependOnInheritedWidgetOfExactType<Provider<T>>();
    } else {
      return context.getElementForInheritedWidgetOfExactType<Provider<T>>().widget as Provider<T>;
    }
  }

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    //在此簡單返回true,則每次更新都會呼叫依賴其的子孫節點的'didChangeDependencies'。
    return true;
  }
}
複製程式碼

3. 建立StatefulWidget,用來儲存實際顯示的Widget和可被監聽的資料管理類,並根據兩者建立InheritedWidget,並監聽資料管理類的變化。

class ProviderCreator<T extends ChangeNotifier> extends StatefulWidget {
  final T data;
  final Widget child;

  ProviderCreator({
    Key key,
    this.data,
    this.child,
  }) {
    print("ProviderCreator=$hashCode");
  }

  @override
  State<StatefulWidget> createState() {
    return ProviderCreatorState<T>();
  }
}

class ProviderCreatorState<T extends ChangeNotifier> extends State<ProviderCreator> {
  void update() {
    setState(() {});
  }

  @override
  void initState() {
    widget.data.addListener(update);
    super.initState();
  }

  @override
  void dispose() {
    widget.data.dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(ProviderCreator<ChangeNotifier> oldWidget) {
    //當Provider更新時,如果新舊資料不"==",則解綁舊資料監聽,同時新增新資料監聽
    if (oldWidget.data != widget.data) {
      oldWidget.data.dispose();
      widget.data.addListener(update);
    }
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    print("""CustomInheritedWidgetCreatorState build
        \twidget=${widget.hashCode}
        \twidget.data.hashCode=${widget.data.hashCode}
        \twidget.child=${widget.child.hashCode}""");
    return Provider<T>(
      data: widget.data,
      child: widget.child,
    );
  }
}
複製程式碼

示例

然後,我們用一個示例來試一下我們剛才自己寫的Provider 我們還用上篇文章裡的示例,用一個計數器程式來測試。

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter InheritWidget',
      home: Scaffold(
        appBar: AppBar(),
        body: Center(
          child: BodyWidget(),
        ),
      ),
    );
  }
}

class BodyWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return BodyWidgetState();
  }
}

class BodyWidgetState extends State<BodyWidget> {
  Counter counter = Counter();

  @override
  Widget build(BuildContext context) {
    return ProviderCreator<Counter>(
      data: counter,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          //依賴
          DependOnInheritedWidget<Counter>(),
          //不依賴
          Builder(builder: (context) {
            return Text(Provider.of<Counter>(context, false).data.toString());
          }),
          Builder(builder: (context) {
            return CustomRaisedButton(
              onPressed: () {
                //不依賴
                Provider.of<Counter>(context, false).data.increment();
              },
              child: Text("數字+1"),
            );
          })
        ],
      ),
    );
  }
}

class Counter extends ChangeNotifier {
  int num = 0;

  void increment() {
    num++;
    notifyListeners();
  }

  @override
  String toString() {
    return "$num";
  }
}

class DependOnInheritedWidget<T extends ChangeNotifier> extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return DependOnInheritedWidgetState<T>();
  }
}

class DependOnInheritedWidgetState<T extends ChangeNotifier> extends State<DependOnInheritedWidget> {
  @override
  Widget build(BuildContext context) {
    print("DependOnInheritedWidgetState build");
    return Text(Provider.of<T>(context, true).data.toString());
  }

  @override
  void didChangeDependencies() {
    print("DependOnInheritedWidgetState didChangeDependencies");
    super.didChangeDependencies();
  }
}

class CustomRaisedButton extends RaisedButton {
  const CustomRaisedButton({
    @required VoidCallback onPressed,
    Widget child,
  }) : super(onPressed: onPressed, child: child);

  @override
  Widget build(BuildContext context) {
    print("CustomRaisedButton build");
    return super.build(context);
  }
}
複製程式碼

當我們點選“數字+1”這個按鈕的時候,會在日誌列印裡發現如下資訊:

I/flutter (  489): CustomInheritedWidgetCreatorState build
I/flutter (  489):         	widget=136741630
I/flutter (  489):         	widget.data.hashCode=597399651
I/flutter (  489):         	widget.child=443053943
I/flutter (  489): Provider=611638398
I/flutter (  489): DependOnInheritedWidgetState didChangeDependencies
I/flutter (  489): DependOnInheritedWidgetState build
複製程式碼

說明CustomRaisedButton不再build了,注意:Providerof方法中的dependOn引數,為true說明呼叫了該方法的widget依賴了InheritedWidget,為false就沒有依賴InheritedWidget,具體的可以看dependOnInheritedWidgetOfExactTypegetElementForInheritedWidgetOfExactType這兩個方法的原始碼,這裡不再贅述。

至此,我們做了一個簡單的Provider,大功告成!

相關文章