DEV Community

loading...
Cover image for Using Selector in Provider

Using Selector in Provider

aseemwangoo profile image aseem wangoo Updated on ・7 min read

In case it helped :)
Pass Me A Coffee!!

Begin…

View the demo here

Website: https://web.flatteredwithflutter.com/#/

We will cover briefly about

  1. Selector 
  2. Using Selector with Provider

Article here: https://flatteredwithflutter.com/using-selector-in-provider/

Current Status

Let's start by describing what we have currently. In our demo, we have an input form with 3 fields:

  • Name Field
  • Age Field
  • Email Field

Using Selector in Provider
Using Selector in Provider


There are 3 text fields, which basically show us the values inside each of the input fields.


Approach 1 (All this time I took)

Using Selector in Provider
Using Selector in Provider

As per this approach, we would have a 

  • ChangeNotifierProvider, at the top of the widget tree.
  • Followed by a consumer, before we start our form
  • Finally, our Form UI

Things to consider:

Your Form fields will undergo unnecessary builds. Let me explain.

For instance, say you are entering the age field, other fields (name and email field), will continue rebuilding since the Consumer widget is above the Form.

This is true for any other field also.

As per the Consumer documentation:

builder may be called multiple times (such as when the provided value change).

In our case, we just have 3 fields inside FormUI, so we can go with approach1, but what if we had a complex UI, think about it!

Approach 2 (Using Selector in Provider)

Using Selector in Provider
Using Selector in Provider

When I started to use Provider in Flutter, most of the times, it was either

Now we will use a Selector.

In this approach, we have ChangeNotifierProvider at the top, followed by our UI.

So, for adding values to our model (ChangeNotifier model, UserData) we use

context.read<UserData>()

Using Selector in Provider
Using Selector in Provider

This is an extension included in Provider, now we have the input inside our model(UserData).

NotifyListeners

Note: Setting of the value, notifies the model since the setter implements notifyListeners.

set emailAddress(String emailAddress) {
_emailAddress = emailAddress;
notifyListeners();
}
// SIMILAR FOR NAME AND AGE

Similarly, we do this for all the input fields (name, age, and email)

Let's say, we want to display the values entered in these fields in real-time.

Selector in Provider
Selector in Provider
  • Email Text should only listen to Email Field.
  • Name + Age Text should only listen to changes in Name and Age Fields
  • For displaying all values of the form, we need to listen to every field

Email Text

final _email =
context.select<UserData, String>((model) => model.emailAddress);
return Text('(Listen to Email) Email : $_email');

We use context.select.

As per documentation

Watch a value of type [T] exposed from a provider, and listen only partially to changes. 

By using [select], instead of watching the entire object, the listener will rebuild only if the value returned by selector changes.

Hence for email text, we select only the email field and get notified when it changes. 

Name + Age Text

Here, we want to listen to two fields from our notifier model. 

Time to introduce a new package tuple.

This package gives us options to select the number of values and comes recommended by Provider. Currently supports 7 values.

To select multiple values without having to write a class that implements ==, the easiest solution is to use a "Tuple" from tuple

Since we need 2 fields, we use Tuple2

Tuple2 example
Tuple2 example

Here, we use the Selector widget

An equivalent to [Consumer] that can filter updates by selecting a limited amount of values and prevent rebuild if they don’t change.

Criteria: Tuple2 of Name field and age field, Tuple2<String, int>.

Hence, inside our builder, we can retrieve these values by 

data.item1 // For name, since First value of Tuple is String

data.item2 // For age, since Second value of Tuple is int

All Text

Here, we need to listen to all the values inside our notifier model. This turns out to be a good use-case for Consumer.

Consumer<UserData>(
  builder: (_, data, __) {
    return Text(
          '(Listening to all) Name : ${data.name} >>> Age : ${data.age} >>> Email${data.emailAddress}',
    );
  },
);

In case it helped :)
Pass Me A Coffee!!

Hosted URL : https://web.flatteredwithflutter.com/#/

Source code for Flutter Web App..

Discussion (2)

pic
Editor guide
Collapse
devkhalyd profile image
Rolando

Hi, first that all, I'm from Mexico, sorry for my bad English.

Selector<MenuSpotifyNotifier, Tuple2<int, String>>(
        selector: (_, d) => Tuple2(d.currentPos, d.titles[pos]),
        builder: (_, d, __) {
          int cPos = d.item1;
          String textIcon = d.item2;
          FaIcon icon = disabledIcon;
          bool isSelected = cPos == pos;
          if (isSelected) icon = enabledIcon;

          Log.console('Rebuild the UI in $textIcon');

          return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              IconButton(
                icon: icon,
                onPressed: () =>
                    Provider.of<MenuSpotifyNotifier>(context, listen: false)
                        .setCurrentPos(pos),
              ),
              Text(
                textIcon,
                style: TextStyle(
                    color: isSelected ? Colors.white : Colors.grey,
                    fontSize: 11.5),
              )
            ],
          );
        },
      );
Enter fullscreen mode Exit fullscreen mode

This code is for change the state when an item is pressed so the Log.console method show me when a item is rebuild. What I expect? If the item 0 is pressed this only should be rebuild, but that is not the case. All the items are rebuild.

Here an example:

dev-to-uploads.s3.amazonaws.com/i/...

Am I doing something wrong? Or Is this the behavior expected?

Collapse
aseemwangoo profile image
aseem wangoo Author

For the selector, theoratically it should change only if d.currentPos or d.titles[pos] changes