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
:
,>
A
We get it from the top levelProvider
The type ofS
It’s the specific type that we care about, which is what we getProvider
The types that are really useful to us in theselector
Returns this type in. thisSelector
The refresh range is also from the wholeProvider
It becomes S.
Take a quick look at the properties in Selector:
- Selector: That’s one
Function
, into the conference will take us to the topprovider
Pass in, and then return what we care aboutS
. shouldRebuild
: This property will be storedselector
The filtered value, which is equal toselector
The returnedS
And take the new one after receiving the noticeS
With the cacheS
Compare and judge thisSelector
Whether to rebuild, defaultpreview! =next
Refresh, if yescollection
.selector
Make an in-depth comparison.- Builder:
Consumer
Again, this returns the control to build, the second argumentprovider
That’s what we just didselector
In returnS
. - Child: This is used to optimize parts that don’t need to be refreshed, as we said earlier
Consumer
I’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.