Flutter status management Provider

In fluent, state management is the top priority. Whenever we talk about this topic, there are always endless words.

After formally introducing the Provider, why do we need state management. If you already know this clearly, it is recommended to skip this section directly.
If our application is simple enough, as a declarative framework, you may only need to map data into views. You may not need state management, as shown below.


But with the increase of functions, your application will have dozens or even hundreds of states. At this time, your application should be like this.

What the hell is this. It's hard for us to clearly test and maintain our state, because it looks too complicated! Moreover, multiple pages will share the same state. For example, when you enter an article and exit to the external thumbnail display, the number of likes also needs to be displayed externally. At this time, you need to synchronize the two states.
In fact, from the beginning, fluent provided us with a state management method, that is, stateful widget. But we soon found that it was the culprit of the above reasons.
When a State belongs to a specific Widget and communicates among multiple widgets, although you can use callback to solve it, when the nesting is deep enough, we add a lot of terrible garbage code.
At this time, we urgently need an architecture to help us clarify these relationships, and the state management framework came into being.

 

What is a Provider

By using the Provider instead of writing the inheritedwidget manually, you will get the code to consult the allocation, delay loading, and reduce the number of hits each time you create a new class.

First, add in yaml. For the specific version number, refer to: Official Provider pub , the current version number is 4.1.3

  
Provider: ^4.1.3

  

 

Then run

flutter pub get

 

Get the latest package locally and import it in the required folder
import 'package:provider/provider.dart';

 

Simple example

We also use the click button to add an example of a number

First, create a Model that stores data

class ProviderModel extends ChangeNotifier {

    int _count=0;
    ProviderModel();
   void plus() {
   /// Notify the listener to refresh when the data changes UI
    _count = _count + 1;
    notifyListeners();
  }
}

 

 

Construct view

/// use Consumer To monitor global refresh UI
Consumer<ProviderModel>(
        builder:
            (BuildContext context, ProviderModel value, Widget child) {
          print('Consumer 0 Refresh');
          _string += 'c0 ';
          return _Row(
            value: value._count.toString(),
            callback: () {
              context.read<ProviderModel>().plus();
            },
          );
        },
        child: _Row(
          value: '0',
          callback: () {
            context.read<ProviderModel>().plus();
          },
        ),
      )

 

Test to see the effect:

Multiple widgets of a single Model are refreshed separately (local refresh)

A single model can refresh multiple widgets on a single page. It is implemented by using selector < model, int >. First, let's look at the constructor:

class Selector<A, S> extends Selector0<S> {
  /// {@macro provider.selector}
  Selector({
    Key key,
    @required ValueWidgetBuilder<S> builder,
    @required S Function(BuildContext, A) selector,
    ShouldRebuild<S> shouldRebuild,
    Widget child,
  })  : assert(selector != null),
        super(
          key: key,
          shouldRebuild: shouldRebuild,
          builder: builder,
          selector: (context) => selector(context, Provider.of(context)),
          child: child,
        );
}

 

Can see SelectorInheritedSelector0,Look againSelectorcruxbuildcode:
class _Selector0State<T> extends SingleChildState<Selector0<T>> {
  T value;
  Widget cache;
  Widget oldWidget;

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    final selected = widget.selector(context);

    var shouldInvalidateCache = oldWidget != widget ||
        (widget._shouldRebuild != null &&
            widget._shouldRebuild.call(value, selected)) ||
        (widget._shouldRebuild == null &&
            !const DeepCollectionEquality().equals(value, selected));
    if (shouldInvalidateCache) {
      value = selected;
      oldWidget = widget;
      cache = widget.builder(
        context,
        selected,
        child,
      );
    }
    return cache;
  }
}

 

 

According to our incoming_ shouldRebuild to determine whether it needs to be updated. If it needs to be updated, execute the widget Build (context, selected, child), otherwise the cached cache is returned When not_ The shouldRebuild parameter is based on the widget The return value of selector (CTX) determines whether it is equal to the old value. If not, the UI is updated.

So it's OK for us not to write shouldRebuild.

Local refresh usage

 
 Widget build(BuildContext context) {
    print('page 1');
    _string += 'page ';
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider Global and local refresh'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Text('Global refresh<Consumer>'),
            Consumer<ProviderModel>(
              builder:
                  (BuildContext context, ProviderModel value, Widget child) {
                print('Consumer 0 Refresh');
                _string += 'c0 ';
                return _Row(
                  value: value._count.toString(),
                  callback: () {
                    context.read<ProviderModel>().plus();
                  },
                );
              },
              child: _Row(
                value: '0',
                callback: () {
                  context.read<ProviderModel>().plus();
                },
              ),
            ),
            SizedBox(
              height: 40,
            ),
            Text('Local refresh<Selector>'),
            Selector<ProviderModel, int>(
              builder: (ctx, value, child) {
                print('Selector 1 Refresh');
                _string += 's1 ';
                return Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text('Selector<Model,int>Times:' + value.toString()),
                    OutlineButton(
                      onPressed: () {
                        context.read<ProviderModel>().plus2();
                      },
                      child: Icon(Icons.add),
                    )
                  ],
                );
              },
              selector: (ctx, model) => model._count2,
              shouldRebuild: (m1, m2) {
                print('s1: $m1 $m2 ${m1 != m2 ? 'Not equal, this refresh' : 'The data are equal, and will not be refreshed this time'}');
                return m1 != m2;
              },
            ),
            SizedBox(
              height: 40,
            ),
            Text('Local refresh<Selector>'),
            Selector<ProviderModel, int>(
              selector: (context, model) => model._count3,
              shouldRebuild: (m1, m2) {
                print('s2: $m1 $m2 ${m1 != m2 ? 'Not equal, this refresh' : 'The data are equal, and will not be refreshed this time'}');
                return m1 != m2;
              },
              builder: (ctx, value, child) {
                print('selector 2 Refresh');
                _string += 's2 ';
                return Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text('Selector<Model,int>Times:' + value.toString()),
                    OutlineButton(
                      onPressed: () {
                        ctx.read<ProviderModel>().plus3();
                      },
                      child: Icon(Icons.add),
                    )
                  ],
                );
              },
            ),
            SizedBox(
              height: 40,
            ),
            Text('Refresh times and order:↓'),
            Text(_string),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                OutlineButton(
                  child: Icon(Icons.refresh),
                  onPressed: () {
                    setState(() {
                      _string += '\n';
                    });
                  },
                ),
                OutlineButton(
                  child: Icon(Icons.close),
                  onPressed: () {
                    setState(() {
                      _string = '';
                    });
                  },
                )
              ],
            )
          ],
        ),
      ),
    );
  }

 

 

effect:

When we click local refresh s1 and execute the build of s1, s1 is not equal and s2 is not equal. Output:

flutter: s2: 5 5 The data are equal, and will not be refreshed this time
flutter: s1: 6 7 Equal, refresh this time
flutter: Selector 1 Refresh
flutter: Consumer 0 Refresh

 

 

When you click S2, the values of S2 are not equal, refresh the UI, and the data of S1 are equal, do not refresh the UI

flutter: s2: 2 3 Not equal, this refresh
flutter: selector 2 Refresh
flutter: s1: 0 0 The data are equal, and will not be refreshed this time
flutter: Consumer 0 Refresh

 

 

You can see that the above two consumers refresh each time. Let's explore the reason.

Consumer global refresh

The Consumer inherits the SingleCHildStatelessWidget. When we call notification in the ViewModel, the current widget will be marked as dirty, and then execute the passed builder function in the build. In the next frame, the UI will be refreshed.

While selector < T, s > is executed when it is marked dirty_ The buildWithChild(ctx,child) function in Selector0State_ shouldRebuild to determine whether the widget needs to be executed Builder (CTX, selected, child)

Other uses

Multi model writing method

It only needs to be wrapped in all the superior packages that need models. When we need two models for a page, we usually write as follows:

class BaseProviderRoute extends StatelessWidget {
  BaseProviderRoute({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<ProviderModel>(
          create: (_) => ProviderModel(),
        ),
        ChangeNotifierProvider<ProviderModel2>(create: (_) => ProviderModel2()),
      ],
      child: BaseProvider(),
    );
  }
}

 

 

Of course, it is consistent with a single model.

  
Selector<ProviderModel2, int>(
    selector: (context, model) => model.value,
    builder: (ctx, value, child) {
      print('model2 s1 Refresh');
      _string += 'm2s1 ';
      return Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('Selector<Model2,int>Times:' + value.toString()),
          OutlineButton(
            onPressed: () {
              ctx.read<ProviderModel2>().add(2);
            },
            child: Icon(Icons.add),
          )
        ],
      );
    },
  ),

 

watch && read

The source code of watch is provider Of < T > (this), default provider Of < T > (this) listen=true

static T of<T>(BuildContext context, {bool listen = true}){
final inheritedElement = _inheritedElementOf<T>(context);

    if (listen) {
      context.dependOnInheritedElement(inheritedElement);
    }

    return inheritedElement.value;
}

 

 

The read source code is provider Of < T > (this, listen: false), watch / read is simply written and has no advanced structure.

When we want to monitor the change of value, we use watch. When we want to call the function of model, we use read

reference resources

  • Code warehouse: https://github.com/ifgyong/flutter-example
  • Official Provider: https://github.com/rrousselGit/provider

Tags: iOS Flutter

Posted by bickyz on Mon, 23 May 2022 06:06:52 +0300