preface
Enter this post, you can first look at the introduction of ha.
- The Flutter state management Provider(I) is easy to use
- Flutter state management Provider(ii) source analysis
- 3. Develop application based on The Provider
Selector
introduce
Dart has an introduction to selector
/// {@template provider.selector}./// {@endtemplate}
class Selector<A.S> extends Selector0<S> {}class Selector0<T> extends SingleChildStatefulWidget {
final ValueWidgetBuilder<T> builder;
final T Function(BuildContext) selector;
final ShouldRebuild<T> _shouldRebuild;
}
Copy the code
We get the gist of it.
- Selector is equivalent to Cosumer, but it prevents rebuild without changing some values.
- Selector method: Selector retrieves shared data using Provider. Of. Perform the selector method and return as much data as the build needs, as little data as possible.
- ShouldRebuild: default to determine the equality of the two S to determine whether to rebuild. And it also provides a custom shouldRebuild method to determine, the argument is before and after the two S.
- S: Selector data must be immutable. So selector usually returns a collection or a class that overrides “==”. [tuple] (pub.dev/packages/tu…) is recommended if multiple values of selector are required.
Immutable: Objects of a class are immutable if their state does not change after they are created by constructors. The assignment of all its member variables is done only in the constructor and does not provide any setter methods for external classes to modify.
/ / the tuple, for example
Selector<Foo, Tuple2<Bar, Baz>>(
selector: (_, foo) => Tuple2(foo.bar, foo.baz),
builder: (_, data, __) {
return Text('${data.item1} ${data.item2}'); })Copy the code
Using an example
Now, here’s a Selector usage scenario. Demo repository address entry: main_provider. Dart — Selector used
The effect
There are three letter buttons on the page, and count the number of times each button is clicked. Click the button, the number on the button +1.
code
// First we have a model, do data processing, ChangeNotifier used in the Provider
class CountModel extends ChangeNotifier {
// Key is a letter and value is the count of clicks
Map<String.int> contentMap = SplayTreeMap();
// Initialize the data
initData() {
contentMap["a"] = 0; contentMap["b"] = 0; contentMap["c"] = 0;
}
// Increase the number of clicks on the letter button
increment(String content) {
contentMap[content] = contentMap[content] + 1;
// Notification refreshnotifyListeners(); }}class SelectorDemoWidget extends StatefulWidget {
SelectorDemoWidget({Key key}) : super(key: key);
@override
_SelectorDemoWidgetState createState() => _SelectorDemoWidgetState();
}
class _SelectorDemoWidgetState extends State<SelectorDemoWidget> {
CountModel _model;
@override
void initState() {
super.initState();
// Initialize the data
_model = newCountModel().. initData(); }@override
Widget build(BuildContext context) {
// Build a set of alphabetic buttons (countitemWidgets)
List<CountItemWidget> _children = _model.contentMap.keys
.map((key) => CountItemWidget(content: key))
.toList();
return Scaffold(
appBar: AppBar(title: Text(The Selector "sample"),),
//ChangeNotifierProvider is a common modebody: ChangeNotifierProvider.value( value: _model, child: ListView(children: _children), )); }}// The letter button
class CountItemWidget extends StatelessWidget {
final String content;
CountItemWidget({this.content});
@override
Widget build(BuildContext context) {
print("CountItemWidget:build");
return Container(
height: 80,
padding: EdgeInsets.all(15),
alignment: Alignment.center,
child: RaisedButton(
onPressed: () =>
Provider.of<CountModel>(context, listen: false).increment(content),
child: Selector<CountModel, int> (// Get the corresponding letter count from CountModel
selector: (context, model) => model.contentMap[content],
// If count is not equal, refreshshouldRebuild: (preCount, nextCount) => preCount ! = nextCount, builder: (context, count, child) {print("$content Selector:builder");
return Text("$content : $count"); }),),); }}Copy the code
Source code analysis
A simple example is done. So let’s look at the implementation inside of Selector.
When updating S, is the value of S the same before and after the comparison? If not, rebuild and cache. If yes, use the cache Widget
Interpretation of the
// Some code is omitted
typedef ShouldRebuild<T> = bool Function(T previous, T next);
class Selector<A.S> extends Selector0<S> {
Selector({
Key key,
@required ValueWidgetBuilder<S> builder,
// How to select A selector from A->S
@required S Function(BuildContext, A) selector,
ShouldRebuild<S> shouldRebuild,
Widget child,
}) : assert(selector ! =null),
super(
key: key,
shouldRebuild: shouldRebuild,
builder: builder,
// Write provider. of(context) to be more explicit
selector: (context) => selector(context, Provider.of(context)),
child: child,
);
}
class Selector0<T> extends SingleChildStatefulWidget {
final ValueWidgetBuilder<T> builder;
final T Function(BuildContext) selector;
final ShouldRebuild<T> _shouldRebuild;
}
class _Selector0State<T> extends SingleChildState<Selector0<T>> {
//selector method A -> S
T value;
// If you do not rebuild, return the cache Widget
Widget cache;
//oldWidget --> Selector
Widget oldWidget;
@override
Widget buildWithChild(BuildContext context, Widget child) {
CountModel is A, int is S
//1.CountModel model=Provider.of<CountModel>(context);
//2.int count = model.contentMap[content]; The count is selected
final selected = widget.selector(context);
/ / 3. Value = = selected? Value: preCount, selected: nextCount
varshouldInvalidateCache = oldWidget ! = widget ||(widget._shouldRebuild ! =null
&& widget._shouldRebuild.call(value, selected))
||(widget._shouldRebuild == null&&!const DeepCollectionEquality().equals(value, selected));
//4. If the value is not equal, the cache needs to be invalidated.
if (shouldInvalidateCache) {
//5. Generate appropriate widgets and cache values and widgets
value = selected;
oldWidget = widget;
cache = widget.builder(
context,
selected,
child,
);
}
returncache; }}Copy the code
The flow chart
Through the above analysis, we get a flow chart
Applicable scenario
Selector provides more fine-grained refresh control than Cosumer. For a component that only cares about a change in the Model, consider using Selector, global versus local. And what’s really important to note is that the result of the selector, it has to be an immutable object. If the same object only changes its properties, the two values of shouldRebuild should always be equal. Refresh a single scene in the likes post we mentioned in the previous Provider development app, and you can also use Selector
class PostItemWidget2 extends StatelessWidget {
final PostBean post;
final void Function(BuildContext context, PostBean post) click;
const PostItemWidget2({Key key, this.post, this.click}) : super(key: key);
@override
Widget build(BuildContext context) {
print("PostItemWidget2:build");
returnGestureDetector( onTap: () => click? .call(context, post), child: Container( height:80,
child: Row(
children: <Widget>[
Expanded(
child: Text(
"${post? .content}",
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Container(
width: 50,
child: Selector<PostItemChange, bool>( selector: (context, itemChange) => post.isLike, shouldRebuild: (pre, next) => pre ! = next, builder: (context, isLike, child) {return Icon(
Icons.favorite,
color: (isLike ?? false)? Colors.red : Colors.grey, ); }),),],),); }}Copy the code
ItemRefresher
The introduction of
As the Selector library points out
The result of selector must be an immutable object
Sometimes, however, the UI layer tends to use data layer objects directly rather than turning them into UI layer objects. For example, in our example, the PostItemWidget always corresponds to the same PostBean. The difference is that postbean.islike has changed. Also, our Notify refresh is just a simple PostNotifier(ID) and does not contain the full picture of postBeans. You can customize an Item Refresh by referring to Selector. The following one can be referenced
The source code
// Determine whether the zone requires rebuild based on notifier and value
typedef ShouldRebuild<A, T> = bool Function(A notifier, T value);
class ItemRefresher<A.T> extends SingleChildStatefulWidget {
final ShouldRebuild<A, T> _shouldRebuild;
final T value;
ItemRefresher({
Key key,
// value has an initial value,
@required this.value,
ShouldRebuild<A, T> shouldRebuild,
@required this.builder,
Widget child,
}) : assert(builder ! =null),
this._shouldRebuild = shouldRebuild,
super(key: key, child: child);
final ValueWidgetBuilder<T> builder;
@override
_ItemRefresherState<A, T> createState() => _ItemRefresherState<A, T>();
}
class _ItemRefresherState<A.T> extends SingleChildState<ItemRefresher<A.T>> {
Widget cache;
Widget oldWidget;
@override
Widget buildWithChild(BuildContext context, Widget child) {
The logic is similar to selector
A notifier = Provider.of(context);
varshouldInvalidateCache = oldWidget ! = widget || (widget._shouldRebuild ! =null&& notifier ! =null &&
widget._shouldRebuild.call(notifier, widget.value));
if (shouldInvalidateCache) {
oldWidget = widget;
cache = widget.builder(
context,
widget.value,
child,
);
}
returncache; }}Copy the code
use
Define a PostNotifier
class PostNotifier with ChangeNotifier {
int id;
}
Copy the code
Build PostItemWidget
Widget _buildListItem(BuildContext context, PostBean post) {
return ItemRefresher<PostNotifier, PostBean>(
value: post,
// Check if it is the current POSTshouldRebuild: (notifier, value) => (notifier.id ! =null && notifier.id == value.id),
builder: (context, value, child) {
returnPostItemWidget( post: value, click: _skipPostDetail, ); });// return PostItemWidget2(post: post, click: _skipPostDetail);
}
Copy the code
The last
All the sample code is used in the Demo repository address entry: main_provider.dart — Selector