In a previous article I wrote how to implement the BLoC framework of Flutter (juejin.cn/post/684490…) The implementation of BLoC imitates the MVVM development mode in Android development. The Stream of Flutter is used to push data to the UI layer when data changes, and then the UI layer automatically updates the UI. This time we will implement a Provider, which is also the most commonly used state management framework in Flutter. We will implement the Provider ourselves to understand the internal core principles of the Provider.

First, in the previous article (InheritWidget principles: juejin.cn/post/684490…) When you click “number +1” button, the “number +1” button is also refreshed (CustomRaisedButton Build). This is because the setState method we call is a method of the BodyWidgetState class, so the BodyWidget and its Child widget are rebuilt, We need to refresh only the InheritedWidget that relies on the InheritedWidget when the InheritedWidget’s data changes. To do this, we cannot call the BodyWidgetState setState method. Instead, call setState for only one node on the InheritedWidget, which means that the InheritedWidget is the child of a StatefulWidget. Then we write the code step by step:

  1. To create a data management class that can be listened on, why don’t we just let itInheritedWidgetTo be a type that can be listened on? This is becauseInheritedWidgetJust provide the data, and the consumers of the data should have the data and notInheritedWidgetOtherwise data consumer if holdInheritedWidgetIf so, methods to modify specific data are added toInheritedWidgetData types are so diverse that it is impossible to write them allInheritedWidget, so we need to create a data management class that can be listened to, so that when data changes, the data management class method is called, and the data management class notifies the class listener
  2. createInheritedWidgetA subclass that holds a data management class that can notify all dependent classes after rebuildingwidget
  3. createStatefulWidgetLet’s call it thetaProviderCreatorTo save the actual displayWidgetAnd data management classes that can be listened to, and created based on bothInheritedWidgetAnd listen for changes to the data management class. This allows you to pass through the data management class when the data changesProviderCreatorThe correspondingStatecallsetStateTo make theInheritedWidgetReconstruction.InheritedWidgetIf you rebuildProviderCreatorThe correspondingElementIf it wasn’t destroyed, then thisProviderCreatorThe internal data management class that can be listened to and the actual displaychildIt is cached (note: thischildThat’s what we passed in to actually displaywidgetRather thanProviderCreatorThe correspondingStatethebuildMethodwidget)

Start our coding

1. Create a listening data management class

When subclassing the InheritedWidget, the data in it can be of any type, but we need to notify listeners when data changes. Therefore, we restrict the data types in flutter to be of any type that can be listened on. Inside flutter, there is a class called ChangeNotifier. Ideal for being a listener, the code is as follows

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 inlocalListeners) { listener(); }}}}Copy the code

I’ve simplified the code to look like this, so all of our listening data classes need to inherit from this class

2. To createInheritedWidgetA subclass

This class is called 'Provider' because it provides a data management class that can be listened onCopy the code
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");
  }

  // Define a convenient method for widgets in a subtree to get shared data
  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 asProvider<T>; }}@override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    // Simply return true here, and each update calls 'didChangeDependencies' of its descendants.
    return true; }}Copy the code

3. Create aStatefulWidgetTo save the actual displayWidgetAnd data management classes that can be listened to, and created based on bothInheritedWidgetAnd listen for changes to the data management class.

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() {
    returnProviderCreatorState<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) {
    // When the Provider updates, if the old and new data do not "==", unbind the old data listener and add the new data listener
    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}"" ");
    returnProvider<T>( data: widget.data, child: widget.child, ); }}Copy the code

The sample

Then, let’s try out the Provider we just wrote ourselves with an example and test it with a counter program using the example from the previous article.

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() {
    returnBodyWidgetState(); }}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>[
          / / rely on
          DependOnInheritedWidget<Counter>(),
          / / do not rely on
          Builder(builder: (context) {
            return Text(Provider.of<Counter>(context, false).data.toString());
          }),
          Builder(builder: (context) {
            return CustomRaisedButton(
              onPressed: () {
                / / do not rely on
                Provider.of<Counter>(context, false).data.increment();
              },
              child: Text("Digital + 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() {
    returnDependOnInheritedWidgetState<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); }}Copy the code

When we click the “number +1” button, we will find the following information in the log print:

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
Copy the code

The CustomRaisedButton is no longer built. If the value is true, the InheritedWidget is dependent on the InheritedWidget. If the value is false, the InheritedWidget is not dependent on the Provider. Specific can see dependOnInheritedWidgetOfExactType and getElementForInheritedWidgetOfExactType the source code of these two methods, there is no longer here.

So far, we have done a simple Provider, done!