preface

I am really sorry that the update is too slow because the project is too busy recently. Without further ado, let’s get started.

Selector

Read the document

I didn’t actually plan on talking about selectors, but a friend of mine wanted me to talk about it, so LET’s start with selectors.

In general, a Selector is the same thing as a Consumer, and it gets its data through provider. of, except that a Selector, as its name suggests, filters out unnecessary updates to prevent rebuilds, meaning that it only updates data that meets the criteria.

So let’s look at the definition of Selector:

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,
        );
}
Copy the code

First, explain the generics in Selector

:
,>

  • AWe get it from the top levelProviderThe type of
  • SIt’s the specific type that we care about, which is what we getProviderThe types that are really useful to us in theselectorReturns this type in. thisSelectorThe refresh range is also from the wholeProviderIt becomes S.

Take a quick look at the properties in Selector:

  • Selector: That’s oneFunction, into the conference will take us to the topproviderPass in, and then return what we care aboutS.
  • shouldRebuild: This property will be storedselectorThe filtered value, which is equal toselectorThe returnedSAnd take the new one after receiving the noticeSWith the cacheSCompare and judge thisSelectorWhether to rebuild, defaultpreview! =nextRefresh, if yescollection.selectorMake an in-depth comparison.
  • Builder:ConsumerAgain, this returns the control to build, the second argumentproviderThat’s what we just didselectorIn returnS.
  • Child: This is used to optimize parts that don’t need to be refreshed, as we said earlierConsumerI’ve said it before.

By default, whether or not the Builder in a Selector is called to update depends on the comparison between the old and new data in the Selector, and if the old and new data is a collection, So the comparison result is obtained through the DeepCollectionEquality in the collection package.

This default behavior can be overridden by customizing the shouldRebuild callback.

Note: Selected data must be immutable, otherwise the Selector might think that nothing has changed and therefore not call the Builder again.

So, the selector should return either a Set (List/Map/Set/Iterable) or a class that overrides ==.

But sometimes we don’t want to rewrite ==, and the easiest way to do the same is to use Tuple:

Selector<Foo, Tuple2<Bar, Baz>>(
  selector: (_, foo) => Tuple2(foo.bar, foo.baz),
  builder: (_, data, __) {
    return Text('${data.item1}  ${data.item2}'); })Copy the code

In the above example, builder will only be called again if foo.bar or foo.bar changes.

Learn how to use a Tuple by yourself.

For example

The above mentioned is nothing more than a list of official documents, we talk about specific applications.

Just to give you a quick overview of what we’re going to do, it’s very simple, we have a list of items, and when we click on an item, it shows up in the cart. We need to set the “isSelected” field for the Commodity to be added to the shopping cart. When we click on the Commodity, we will update the “isSelected” field. This will inform the Flutter to update the UI. If a ChangeNotifier is used, a notifyListeners are called. This is what we need, but is it really necessary to refresh all the commodities that depend on this Provider in this way?

At this point we can consider using Selector for optimization — filtering out unnecessary refreshes.

First, we create a CommodityProvider:

class CommodityProvider with ChangeNotifier {
  List<Commodity> _commodityList =
      List.generate(10, (index) => Commodity('Commodity Name_$index'.false));

  get commodityList => _commodityList;

  get length => commodityList.length;

  addToCart(int index) {
    Commodity commodity = commodityList[index];
    commodityList[index] = Commodity(commodity.name, !commodity.isSelected);
    notifyListeners();
  }
}
Copy the code

Commodity (); Commodity (); Commodity (); Commodity (); The _commodityList is generally obtained from the server in practice, so we write it down for convenience. The addToCart method adds or removes the item from the cart. When we click on the item corresponding to the index, we add it to the cart or remove it from the cart. We’re going to render the entire list via the commodityList, and Length is the length of the list of items.

And then we’re going to see if the Selector actually filters the refresh. Next, we will provide the data through the ChangeNotifierProvider on the top-level page.

class CommodityListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    returnChangeNotifierProvider( create: (_)=>CommodityProvider(), child: ourWidget, ); }}Copy the code

Obviously, in order to implement this list we have to know the length of the list, and length is applied to the whole list, but we don’t want it to refresh every time something changes in the list, so now we’re going to filter out all the refreshes, and implement a “Consumer” that doesn’t refresh by Selector.

    Selector<CommodityProvider, CommodityProvider>(
        shouldRebuild: (pre, next) => false,
        selector: (_, provider) => provider,
        builder: (context, provider, child) {
          print("build selector 1");
          returnourWidget; },),Copy the code

Here, the generic A and S in Selector are commodityProviders, because we want to get the whole CommodityProvider, but we’ve rewritten the shouldRebuild to avoid unnecessary refreshes.

Let’s implement our list of items:

ListView.builder(
              itemCount: provider.length,
              itemBuilder: (BuildContext context, int index) =>
                  Selector<CommodityProvider, Commodity>(
                    selector:
                        (BuildContext context, CommodityProvider provider) =>
                            provider.commodityList[index],
                    builder: (BuildContext context, Commodity commodity,
                        Widget child) {
                      print("build item $index");
                      return ListTile(
                        onTap: () => provider.addToCart(index),
                        title: Text("${commodity.name}"), trailing: Icon(commodity.isSelected ? Icons.remove_shopping_cart : Icons.add_shopping_cart), ); })); }Copy the code

We can see here that the selector returns provider.commodityList[index], which is a specific item, so each item only needs to care about its own property, and then the refresh scope of the selector is limited to the current item, At the same time we added logs to the Builder for Selector

to verify the filter refresh mechanism.
,>

Come on! Run it, click on a few random items, and look at the log:

I/flutter (29438): build selector 1
I/flutter (29438): build item 0
I/flutter (29438): build item 1
I/flutter (29438): build item 2
I/flutter (29438): build item 3
I/flutter (29438): build item 4
I/flutter (29438): build item 5
I/flutter (29438): build item 6
I/flutter (29438): build item 7
I/flutter (29438): build item 8
I/flutter (29438): build item 9
I/flutter (29438): build item 7
I/flutter (29438): build item 5
I/flutter (29438): build item 4
Copy the code

How’s that? Now that we’ve only refreshed the items we clicked on, avoiding a refresh of the entire list, we’ve taken another small step towards performance optimization.

For more details, please listen to the next episode

As the third installment in the Provider series, the content is still very simple, and I’m running out of time.

To be continued… I don’t expect you to decide.