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