Book connected to wen

Management of Flutter state: In Provider4 Introductory Tutorial (I), we have a preliminary understanding of state management and Provider, and also learn the use of ChangeNotifierProvider and Consumer. However, due to the limited time, we shut off the mention of Consumer, and even forgot to hit the advertisement. Now let’s begin by talking about consumers.

Why is it Consumer?

Consumer2-6

If you are careful about using Consumer, you may notice that there are several magic classes:

  • Consumer2
  • Consumer3
  • Consumer4
  • Consumer5
  • Consumer6

Consumer

Not really. This is actually the Consumer family, consisting of six members. The number after “Consumer” represents the number of data classes a Consumer can receive.

Oh, my God, that’s what it means. So why isn’t there a Consumer1000? Dear, I suggest you to ask the author, I am afraid of being killed. So if there is such a need, do it yourself.

Speaking of which, let’s talk briefly about consumers.

The entire Consumer family uses the Builder pattern and notifies the Builder when a Consumer receives an update. And builder is actually a Function, also can be said to be a lamda. In the case of Conumer, its Builder is defined as Widget (BuildContext Context, T model, Widget Child), which takes three parameters, where T is the data type that can be received, T is the generic T in Consumer

, so make sure you provide generics when using Consumer. In the previous code, for example, Consumer

means that the Consumer receives the MyChangeNotifier instance. Similarly, Consumer2

can receive both A and B data, as can be seen from its Builder definition: Widget Function(BuildContext Context, A value, B value2, Widget Child).
,b>

The Child in Builder is used to build parts that are irrelevant to the data model, and is not redrawn in multiple runs of Builder.

By definition, it’s time to say Consumer3, but considering that they’re all pretty much the same, I won’t add up to the word count. See the source code for details.

Why recommendedConsumer

As I said earlier:

There is no magic in Consumer itself, no fancy implementation. Simply use provider.of in a new control and delegate the build method of that control to the Builder. This Builder will be called multiple times. It’s that simple.

But I also said that Consumer can give us performance optimization because Consumer can give us granular redraw.

Remember that when using Provider. Of, unless we set Listen :false, the BuildeContext related controls in Provider. Of are rebuilt whenever the data received in Provider. This is of course the desired behavior, but sometimes it can cause unnecessarily excessive redrawing.

Let’s look at an example:

 @override
 Widget build(BuildContext context) {
   return FooWidget(
     child: BarWidget(
       bar: Provider.of<Bar>(context),
     ),
   );
 }
Copy the code

In the above code, only BarWidget relies on the data returned in provider.of. However, when the Bar changes, both the BarWidget and FooWidget are redrawn.

Ideally, only barWidgets should be redrawn. One solution is to use Consumer.

To implement the approach we just described, we’ll wrap controls that rely on the Provider with Consumer.

 @override
 Widget build(BuildContext context) {
   return FooWidget(
     child: Consumer<Bar>(
       builder: (_, bar, __) => BarWidget(bar: bar),
     ),
   );
 }
Copy the code

Now, if the Bar is to be updated, only the BarWidget is redrawn. But what if the FooWidget relies on a provider? Such as:

 @override
 Widget build(BuildContext context) {
   return FooWidget(
     foo: Provider.of<Foo>(context),
     child: BarWidget(),
   );
 }
Copy the code

Remember what I said about Child? Yes, that’s it, go to the code:

 @override
 Widget build(BuildContext context) {
   return Consumer<Foo>(
     builder: (_, foo, child) => FooWidget(foo: foo, child: child),
     child: BarWidget(),
   );
 }
Copy the code

In this example, the BarWidget is redrawn outside of the Builder. The BarWidget instance is then passed to the Builder as the first parameter.

This means that when Builder is called repeatedly, the Consumer does not create new instances of BarWidget. This lets Flutter know that it does not have to redraw the BarWidget. So in this way, when Foo is updated, only the FooWidget is redrawn.

If you are interested, you can implement the above sample code and add a few print to build() to verify that this is true.

Consumer summary

In summary, a Consumer has two main uses:

  • The Consumer allows us to get data from the Provider when there is no Provider specified in our BuildContext.
  • Improve performance

Therefore, I strongly recommend using Consumer because it can improve performance. Flutter does have many optimizations, but many are not the whole story. Performance improvements are more dependent on how we implement Flutter.

MultiProvider

We have been talking about the situation of one Provider per page, but in fact, many large-scale applications, it may be more than one Provider, dozens of providers is also possible, then we will want to nest large method:

Provider<Something>(
  create: (_) => Something(),
  child: Provider<SomethingElse>(
    create: (_) => SomethingElse(),
    child: Provider<AnotherThing>(
      create: (_) => AnotherThing(),
      child: someWidget,
    ),
  ),
),
Copy the code

Is a sentence that begins with an F and ends with a U about to pop out again? Don’t worry, the authors have thoughtfully designed MultiProvider:

MultiProvider(
  providers: [
    Provider<Something>(create: (_) => Something()),
    Provider<SomethingElse>(create: (_) => SomethingElse()),
    Provider<AnotherThing>(create: (_) => AnotherThing()),
  ],
  child: someWidget,
)
Copy the code

Of course, these two pieces of code are exactly equivalent, so there’s no dark magic in MultiProvider, except that it doesn’t make the code look ugly.

Note: Consumer can also be used in the MultiProvider. But the Consumer must return the Child it created in the control tree, the Builder in the Builder.

MultiProvider(
  providers: [
    Provider(create: (_) => Foo()),
    Consumer<Foo>(
      builder: (context, foo, child) =>
        Provider.value(value: foo.bar, child: child),
    )
  ],
);
Copy the code

I have a question

When we talked about MultiProvider, a question popped into our head:

  • Can be found in aBuildContextTo get more than one of the same data typesprovider?

It looks like this in code:

Provider<String>(
  create: (_) => 'China',
  child: Provider<String>(
    create: (_) => 'dalian', child: ... ,),),Copy the code

The answer is no. When there are multiple providers of the same data type, a control can obtain only one: the nearest provider.

So, we have to be very specific about their type:

Provider<Country>(
  create: (_) => Country('China'),
  child: Provider<City>(
    create: (_) => City('dalian'), child: ... ,),),Copy the code

Emmmm, it looks good.

For more details, please listen to the next episode

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

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