preface

If you are already familiar with providers, you can skip this section and go directly to the process analysis section of Flutter state management Provider(ii) process analysis

The Demo code repository entry is main_provider.dart

Introduction to the

website

Simple translation/Provider (https://github.com/rrousselGit/provider), the Provider: the combination of dependency injection and state management, and provides the Widget. Use widgets instead of pure Dart objects, such as streams. Because widgets are simple, powerful, and extensible. With providers, you can guarantee: maintainability (mandatory one-way data flow), testability, robustness, and so on. A mixture between dependency injection (DI) and state management, built with widgets for widgets.It purposefully uses widgets for DI/state management instead of dart-only classes like Stream. The reason is, widgets are very simple yet robust and scalable. By using widgets for state management, provider can guarantee:Copy the code
  • maintainability, through a forced uni-directional data-flow
  • testability/composability, since it is always possible to mock/override a value
  • robustness, as it is harder to forget to handle the update scenario of a model/widget

What is a state? What is state management?

If the page is static, get the data, render it and you’re done. There is no need for state management at all. Unfortunately, pages can’t all be static. Pages need to respond to data changes (network data? Data generated by user actions?) Update the UI. At the same time, data changes affect not only components, but also other components within the page, and even other pages within the application

Data is state. The process from data changes to notification interface updates is called state management. State management is about isolating this process as much as possible, making dynamic interfaces as simple as static pages.

What are the state management methods/libraries

- setState
- FutureBuilder/StreamBuilder/BLoc
- Provider/ScopedModel
- redux/Fish-redux
Copy the code

Simple version of the Provider implementation

With the introduction of state management, we can refer to Provider and implement a simple version of Provider through existing components. System components to use:

  • SetState: StatefulWidget is a ready-made way to refresh the UI
  • InheritedWidget: A base widget that gives the widget tree the ability to pass data from the top down. At the same time, data changes can cause widgets that depend on them to be rebuilt.
  • ChangeNotifier: Code model for observer pattern.

setState

The most basic implementation of state management

class _SetStateDemoWidgetState extends State<SetStateDemoWidget> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(setStateDemoTitle),),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text("Count:$count"),
            RaisedButton(
              child: Text("increment"),
              onPressed: () => setState(() => count++), ) ], ), ), ); }}Copy the code

InheritedWidget

Use CountProvider to inherit the InheritedWidget to save data. In the context of CountProvider getElementForInheritedWidgetOfExactType data. The context must be used only with the BuildContext of the CountProvider child widget. The CountProvider lookup is going up through the context. Note that we are simply taking data and are not using the inherent Widget’s ability to control the rebuilding of child widgets.

Class CountProvider extends InheritedWidget {final int count; CountProvider({Key key, this.count, Widget child}) : super(key: key, child: child); @override bool updateShouldNotify(CountProvider old) {return true;
  }
}

class ProviderDemoWidget1 extends StatefulWidget {
  ProviderDemoWidget1({Key key}) : super(key: key);

  @override
  _ProviderDemoWidget1State createState() => _ProviderDemoWidget1State();
}

class _ProviderDemoWidget1State extends State<ProviderDemoWidget1> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(providerDemoTitle1),
      ),
      body: CountProvider(
        count: _count,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Builder(
                builder: (context2) {
                  
                  CountProvider provider = context2
                      .getElementForInheritedWidgetOfExactType<CountProvider>()
                      .widget;
                  return Text("Count:${provider.count}"); },), /// read and display count RaisedButton(child: Text()"increment"),
                onPressed: () => setState(() => _count++), ), Text(providerDemoIntroduction1), ], ), ), ), ); }}Copy the code

Provider(InheritedWidget + ChangeNotifier)

Let’s look at the renderings, and we see three scenarios. Only dependent components update the UI.

  • CountModel encapsulates the count in example 1 and inherits ChangeNotifier with notify capability.
class CountModel extends ChangeNotifier {
  int count;

  CountModel(this.count);

  void increment() { count++; notifyListeners(); }}Copy the code
  • The Provider InheritedWidget encapsulates two types of access: The context. DependOnInheritedWidgetOfExactType and context. GetElementForInheritedWidgetOfExactType. When the InheritedWidget is rebuilt, the former widget is rebuilt, but the latter is not.
class Provider<T extends ChangeNotifier> extends InheritedWidget {
  final T model;

  Provider({Key key, this.model, Widget child}) : super(key: key, child: child);

  static T of<T extends ChangeNotifier>(BuildContext context, bool depend) {
    if (depend) {
      return context.dependOnInheritedWidgetOfExactType<Provider>().model;
    } else {
      Provider provider =
          context.getElementForInheritedWidgetOfExactType<Provider>().widget;
      return provider.model;
    }
  }

  @override
  bool updateShouldNotify(Provider old) {
    return true; }}Copy the code
  • The ChangeNotifierProvider encapsulates setState within the ChangeNotifier by listening on it. In summary, state management (data modification and Widget refresh) is encapsulated in custom objects. External controls no longer need to care about the details of state management.
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
  final Widget child;

  final T model;

  ChangeNotifierProvider({this.child, this.model});

  @override
  _ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}

class _ChangeNotifierProviderState extends State<ChangeNotifierProvider> {
  _ChangeNotifierProviderState();

  _update() {
    setState(() => {});
  }

  @override
  void initState() {
    super.initState();
    widget.model.addListener(_update);
  }

  @override
  void dispose() {
    super.dispose();
    widget.model.removeListener(_update);
  }

  @override
  Widget build(BuildContext context) {
    returnProvider( model: widget.model, child: widget.child, ); }}Copy the code
  • With CountModel, Provider, ChangeNotifierProvider, simple version of state management Provider library also write well, to use below:
class ProviderDemoWidget3 extends StatefulWidget {
  ProviderDemoWidget3({Key key}) : super(key: key);
  
  @override
  _ProviderDemoWidget3State createState() => _ProviderDemoWidget3State();
}

class _ProviderDemoWidget3State extends State<ProviderDemoWidget3> {
  CountModel _countModel = CountModel(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(providerDemoTitle3), ),
      body: ChangeNotifierProvider<CountModel>(
        model: _countModel,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Builder(builder: (context1) {
                return Text(
                    "Count:${Provider.of<CountModel>(context1, true).count}(with dependency)");
              }),
              Builder(builder: (context2) {
                return Text(
                    "Count:${Provider.of<CountModel>(context2, false).count}(no dependence)");
              }),
              RaisedButton(
                  child: Text("increment"), onPressed: () => _countModel.increment()), Text(providerDemoIntroduction3), ], ), ), ), ); }}Copy the code

Generic encapsulation based on the previous version

CountModel was not generic in the previous example, so let’s write a generic version

class Provider<T extends ChangeNotifier> extends InheritedWidget {
  final T model;

  Provider({Key key, this.model, Widget child}) : super(key: key, child: child);

  static T of<T extends ChangeNotifier>(BuildContext context, bool depend) {
    if (depend) {
      return context.dependOnInheritedWidgetOfExactType<Provider>().model;
    } else {
      Provider provider =
          context.getElementForInheritedWidgetOfExactType<Provider>().widget;
      return provider.model;
    }
  }

  @override
  bool updateShouldNotify(Provider old) {
    return true; }}Copy the code
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
  final Widget child;

  final T model;

  ChangeNotifierProvider({this.child, this.model});

  @override
  _ChangeNotifierProviderState createState() => _ChangeNotifierProviderState();
}

class _ChangeNotifierProviderState extends State<ChangeNotifierProvider> {
  _ChangeNotifierProviderState();

  _update() {
    setState(() => {});
  }

  @override
  void initState() {
    super.initState();
    widget.model.addListener(_update);
  }

  @override
  void dispose() {
    super.dispose();
    widget.model.removeListener(_update);
  }

  @override
  Widget build(BuildContext context) {
    returnProvider( model: widget.model, child: widget.child, ); }}Copy the code
class CountModel extends ChangeNotifier {
  int count;

  CountModel(this.count);

  void increment() {
    count++;
    notifyListeners();
  }
}
class ProviderDemoWidget3 extends StatefulWidget {
  ProviderDemoWidget3({Key key}) : super(key: key);

  @override
  _ProviderDemoWidget3State createState() => _ProviderDemoWidget3State();
}

class _ProviderDemoWidget3State extends State<ProviderDemoWidget3> {
  CountModel _countModel = CountModel(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(providerDemoTitle3),
      ),
      body: ChangeNotifierProvider<CountModel>(
        model: _countModel,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Builder(builder: (context1) {
                return Text(
                    "Count:${Provider.of<CountModel>(context1, true).count}(with dependency)");
              }),
              Builder(builder: (context2) {
                return Text(
                    "Count:${Provider.of<CountModel>(context2, false).count}(no dependence)");
              }),
              RaisedButton(
                  child: Text("increment"), onPressed: () => _countModel.increment()), Text(providerDemoIntroduction3), ], ), ), ), ); }}Copy the code

Usage of a real Provider

We found that this is consistent with how we used the simple version of the Provider from the previous example. As described in the introduction is very simple. Of course Provider libraries, maintainability, testability, and extensibility are far more powerful than what we’ve written.

class ProviderDemoWidget4 extends StatefulWidget {
  ProviderDemoWidget4({Key key}) : super(key: key);

  @override
  _ProviderDemoWidget4State createState() => _ProviderDemoWidget4State();
}

class _ProviderDemoWidget4State extends State<ProviderDemoWidget4> {
  CountModel _countModel = CountModel(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(providerDemoTitle4),
      ),
      body: ChangeNotifierProvider.value(
        value: _countModel,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Consumer<CountModel>(
                builder: (contextC, model, child) {
                  return Text("Count:${model.count}(with dependency)");
                },
              ),
              Builder(builder: (context2) {
                return Text(
                    "Count:${Provider.of<CountModel>(context2, listen: false).count}(no dependence)");
              }),
              RaisedButton(
                  child: Text("increment"),
                  onPressed: () => _countModel.increment()),
              Text(providerDemoIntroduction4),
            ],
          ),
        ),
      ),
    );
  }
}

class CountModel extends ChangeNotifier {
  int count;

  CountModel(this.count);

  void increment() { count++; notifyListeners(); }}Copy the code

At this point, we have learned the basic principles and basic usage of the Provider. But our journey has just begun. Know why, but also know why. Next, we analyze the process of Provider state management step by step, starting with setState.

The resources

Flutter of actual combat