头图

Article series

Flutter Provider state management---introduction, class diagram analysis, basic use

Flutter Provider State Management---Analysis of the Use of Eight Providers

Flutter Provider State Management---Analysis of four types of consumer usage

Flutter Provider state management---MVVM architecture combat

Video series

Flutter Provider state management---introduction, class diagram analysis, basic use

Flutter Provider State Management---Analysis of the Use of Eight Providers

Flutter Provider State Management---Analysis of four types of consumer usage

Flutter Provider state management---MVVM architecture combat

Source code warehouse address

github warehouse address

Preface

In the previous article, we gave Provider and used corresponding cases to illustrate their differences. In this section, let’s talk about Provider . If you want to optimize your project structure And their use difference.

Provider.of

Provider.of<T>(context) is Provider . When we use this method to get the value, it will return the nearest T type provider to us, and it will not traverse the entire component tree. Let's look at the code below:

Step 1: Define the model

We have defined a CountNotifier1 , and all the following sample codes will demonstrate around this model

import 'package:flutter/material.dart';

class CountNotifier1 with ChangeNotifier {

  int count = 0;

  void increment() {
    count++;
    notifyListeners();
  }
}

Step 2: Application entry settings

return ChangeNotifierProvider(
  create: (_) => CountNotifier1(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: ConsumerExample(),
  ),
);

Step 3: Use Provider.of

Here, when reading the value and clicking the button +1, it is obtained and used Provider.of<T>()

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/consumer_example/count_notifier1.dart';
import 'package:provider/provider.dart';

class ConsumerExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text("ConsumerExample"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(Provider.of<CountNotifier1>(context).count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(
                top: 20
              ),
              child: ElevatedButton(
                onPressed: (){
                  Provider.of<CountNotifier1>(context).increment();
                },
                child: Text("点击加1"),
              ),
            )
          ],
        ),
      ),
    );
  }
}

Error log

When we run the code, an error will be prompted. It says that we are trying to Widget tree. If you want to fix it, you can change listen to false . This problem actually Provider 4.0.2 , the most important Its default behavior is ture , and the error log is as follows:

======== Exception caught by gesture ===============================================================
The following assertion was thrown while handling a gesture:
Tried to listen to a value exposed with provider, from outside of the widget tree.

This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.

To fix, write:
Provider.of<CountNotifier1>(context, listen: false);

It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.

The context used was: ConsumerExample(dependencies: [_InheritedProviderScope<CountNotifier1?>])
'package:provider/src/provider.dart':
Failed assertion: line 276 pos 7: 'context.owner!.debugBuilding ||
          listen == false ||
          debugIsInInheritedProviderUpdate'

When the exception was thrown, this was the stack: 
........
====================================================================================================

Set listen to false

Provider.of<CountNotifier1>(context, listen: false).increment();

operation result

Consumer

Consumber just calls Widget Prvoider.of , and delegates its construction to the constructor, such as our common Builder . If your Widget relies on multiple models, then it also provides Consumer23456 for convenient calls. The case uses Consumer to modify

Wrap components with Consumer

Which has a builder constructor, when we body changed after the re-run can be found below and use Provider.of result of the same, but there is no need to use the image in Provider.of that every time you have to write a bunch of repetitive code.

There are three attributes:

  • context: current context
  • Provider.of<T>(context): model object
  • child: child component (part that does not need to be refreshed)
body: Consumer(
  builder: (_, CountNotifier1 countNotifier1, child) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(countNotifier1.count.toString(),
               style: TextStyle(
                 color: Colors.red,
                 fontSize: 50
               ),
              ),
          Padding(
            padding: EdgeInsets.only(
              top: 20
            ),
            child: ElevatedButton(
              onPressed: (){
                countNotifier1.increment();
              },
              child: Text("点击加1"),
            ),
          )
        ],
      ),
    );
  },
),

Optimizing Consumer

Optimization method 1: Adjust the position of Consumer as much as possible

We found in the above code that the Center and Column components are also Consumer , but these two components do not need to update the state, and every time we build Widget , we will rebuild the entire body , so we optimize the code The structure looks like this:

body: Center(
  child: Consumer(
    builder: (_, CountNotifier1 countNotifier1, child) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              countNotifier1.count.toString(),
              style: TextStyle(color: Colors.red, fontSize: 50),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () {
                  countNotifier1.increment();
                },
                child: Text("点击加1"),
              ),
            ),
                   Container(
              child: Column(
                children: [
                  Text("更多组件1"),
                  Text("更多组件2"),
                  Text("更多组件3"),
                  Text("更多组件4"),
                  Text("更多组件5"),
                  Text("更多组件6"),
                ],
              ),
            )
          ],
        ),
      );
    },
  )
)

Optimization method 2: Use child for components that do not need to be refreshed but are wrapped by Consumer

For example, we have more components 1-6 or even hundreds of components that do not need to refresh the state, but because you use the Consumer package will cause all refresh, then it will obviously cause performance degradation, you may think of using multiple Consumer packages to refresh. The component is solved, but isn’t that Provider cart before the horse? 06166ae2a19bd0 itself is the robust and code that repeats the code, so at this time we can use Consumer provide us with the child parameter, as follows:

body: Center(
  child: Consumer(
    builder: (_, CountNotifier1 countNotifier1, child) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              countNotifier1.count.toString(),
              style: TextStyle(color: Colors.red, fontSize: 50),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () {
                  countNotifier1.increment();
                },
                child: Text("点击加1"),
              ),
            ),
            child!
          ],
        ),
      );
    },
    child: Container(
      child: Column(
        children: [
          Text("更多组件1"),
          Text("更多组件2"),
          Text("更多组件3"),
          Text("更多组件4"),
          Text("更多组件5"),
          Text("更多组件6"),
        ],
      ),
    ),
  )
),

Selector

Selector class and Consumer similar, just build call Widget provide finer control method, the simple point is, Selector also a consumer, it allows you to define which properties can be prepared from the model.

Let's take an example:

For example, there are 50 attributes in the user model, but I only need to update the age, so I hope I don’t need to rebuild the username , phone number and other components, then Selector is used to solve this problem, let’s look at an example:

Step 1: Define the model

import 'package:flutter/material.dart';

class UserModel6 with ChangeNotifier {

  String name = "Jimi";
  int age = 18;
  String phone = "18888888888";


  void increaseAge() {
    age++;
    notifyListeners();
  }
}

Step 2: Application entry settings

return ChangeNotifierProvider(
  create: (_) => UserModel6(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: SelectorExample(),
  ),
);

Step 3: Use Selector for finer control

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/selector_example/user_model6.dart';
import 'package:provider/provider.dart';

class SelectorExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("SelectorExample"),
      ),
      body: Center(
        child: Selector<UserModel6, int>(
          selector: (_, userModel6) => userModel6.age,
          builder: (_, age, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(age.toString(),
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30
                    )
                ),
                child!
              ],
            );
          },
          child: Padding(
            padding: EdgeInsets.all(20),
            child: ElevatedButton(
              onPressed: (){
                Provider.of<UserModel6>(context, listen: false).increaseAge();
              },
              child: Text("改变年龄"),
            ),
          ),
        ),
      ),
    );
  }
}

operation result

InheritedContext

InheritedContext is Provider built-in extension BuildContext , it does not save the reference of the component's own position in the tree, we see Provider.of<CountNotifier1>(context,listen: false) in the above case, in fact, the of method is to use Flutter search the tree and find the Provider subtype is CountNotifier1 .

Three ways:

  • BuildContext.read: BuildContext.read<CountNotifier1>() can replace Provider.of<CountNotifier1>(context,listen: false) , it will find CountNotifier1 and return it.
  • BuildContext.watch: BuildContext.watch<CountNotifier1>() can replace Provider.of<CountNotifier1>(context,listen: false) , it looks read no different, but with watch you do not need to use Consumer .
  • BuildContext.select: BuildContext.select<CountNotifier1>() can replace Provider.of<CountNotifier1>(context,listen: false) , it looks watch is no different, but with select you do not need to use Selector .

BuildContext.read

The results of the following two usage methods are the same

Use Provider.of<T>()

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';


class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),

      /// Provider.of 获取值
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(Provider.of<CountNotifier2>(context).count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
                child: Text("点击加1"),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Use BuildContext.read

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';


class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),
      /// read 获取值
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(context.read<CountNotifier2>().count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
                child: Text("点击加1"),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

BuildContext.watch

Use Consumer

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';

class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),
      /// Consumer 获取值
      body: Center(
        child: Consumer<CountNotifier2>(
          builder: (_, countNotifier2, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(countNotifier2.count.toString(),
                  style: TextStyle(
                      color: Colors.red,
                      fontSize: 50
                  ),
                ),
                Padding(
                  padding: EdgeInsets.only(top: 20),
                  child: ElevatedButton(
                    onPressed: () => countNotifier2.increment(),
                    child: Text("点击加1"),
                  ),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

Use BuildContext.watch

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';


class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    /// 重要
    final countNotifier2 = context.watch<CountNotifier2>();

    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),
      /// watch 
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(countNotifier2.count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () => countNotifier2.increment(),
                child: Text("点击加1"),
              ),
            ),
          ],
        ),
      ),

    );
  }
}

BuildContext.select

Use Selector

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';

class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),
      /// Selector
      body: Center(
        child: Selector<CountNotifier2, int>(
          selector: (_, countNotifier2) => countNotifier2.count,
          builder: (_, count, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(count.toString(),
                  style: TextStyle(
                      color: Colors.red,
                      fontSize: 50
                  ),
                ),
                child!
              ],
            );
          },
          child: Padding(
            padding: EdgeInsets.only(top: 20),
            child: ElevatedButton(
              onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
              child: Text("点击加1"),
            ),
          ),
        ),
      ),

    );
  }
}

Use BuildContext.select

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';


class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    
    /// 重要
    final count = context.select((CountNotifier2 countNotifier2) => countNotifier2.count);

    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),
      /// select
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
                child: Text("点击加1"),
              ),
            )
          ],
        ),
      ),
    );
  }
}

Summarize

Flutter provides us with a variety of ways to read values. Above we have used and analyzed the four categories of consumers. You can use the corresponding method according to your actual application scenarios.


Jimi
23 声望14 粉丝