State management is of Paramount importance in Flutter, and there is always a lot to talk about when discussing this topic.

In formal introductionScopeModelWhy do we need state management? If this is clear to you, it is advisable to skip this section. If our application is simple enough,FlutterAs a declarative framework, you might just need to map data to views. You probably don’t need state management, as follows.But as you add functionality, your application will have dozens or even hundreds of states. This is what your application should look like.What the hell is this? It’s hard to test and maintain our state clearly because it seems so complicated! There are also multiple pages that share the same status. For example, when you enter a “like” post and exit to the external thumbnail display, the external thumbnail display also needs to show the number of likes, so you need to synchronize the two states.FlutterIt actually gives us a way to manage our state in the first place, which isStatefulWidget. But we soon found out that it was the culprit in the first place. inStateBelonging to a particularWidgetIn the multipleWidgetWhile you can usecallbackSolution, but when the nesting is deep enough, we add an awful lot of junk code. At this point, we urgently needed a framework to help us clarify these relationships, and a state management framework came into being.

What is ScopeModel

ScopeModel allows you to easily pass a data model from a parent Widget to its descendants. In addition, when a model is updated, it rebuilds all the children that use the model. The library was originally extracted from the Fuchsia code base

First add it in YAML

Scoped_model: ^ 0.3.0Copy the code

Then run

flutter pub get
Copy the code

Obtain the latest package to the local, in the required folder to import

import 'package:scoped_model/scoped_model.dart';
Copy the code

We also use the example of clicking a button to add a number

Create a new Basemodel inheritance model

class _BaseModel extends Model {
  int _count = 0;
  void add(int a) {
    _count += a;
    notifyListeners();
  }

  int get count => _count;
}
Copy the code

The widget is then wrapped in the ScopedModel, typically used to include a page or widget that needs to be updated, and the interaction and display content need to be under the same ScopedModel.

class BaseScopedPateRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopedModel<_BaseModel>(
      model: _BaseModel(),
      child: _body(),
    );
  }

  static String get routeName => 'BaseScopedPate';

  Widget _body() => BaseScopedPate();
}
Copy the code

And then we have our UI

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

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

class _BaseScopedPateState extends State<BaseScopedPate> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ScopedModel'),
      ),
      body: ScopedModelDescendant<_BaseModel>(
        builder: (context, child, model) {
          return Text('${model.count}');
        },
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          ScopedModel.of<_BaseModel>(context).add(1); },),); }}Copy the code

The net effect is

The principle of

BaseModel inherits Model from ScopedModel, which inherits Model from Listenable, and uses Microtask to remove listeners when needed.

/// Inheritance can listen to Listenable
abstract class Model extends Listenable{

/// Callback all listeners in the next event loop
 @protected
  void notifyListeners() {
    if(_microtaskVersion == _version) { _microtaskVersion++; scheduleMicrotask(() { _version++; _microtaskVersion = _version; _listeners.toList().forEach((VoidCallback listener) => listener()); }); }}}}Copy the code

Listener add time

ScopedModel

(){} Calls the ModelBuilder widget when executing builder, which implements widget.model.addlisten (_onChange) in initState(){}, Void _onChange() => setState(() {}); .

Listeners have been added at this point, and notifyListeners() are then called in the inherited Model whenever a refresh is needed. Refresh in this way is also business independent, similar to MVVM.

Local refresh

When the page is a little more complex, a simple click event causes the entire page to refresh, which is not what we want to see, so how do we implement a local refresh?

We define multiple ModelAs and modelB to each inherit model, and then listen at the top of the widget, as follows, and then in the separate Builder.

class _BaseModel extends Model {
  int _count = 0;
  void add(int a) {
    _count += a;
    notifyListeners();
  }

  int get count => _count;
}

class _BaseModel2 extends Model {
  int _count = 0;
  void add(int a) {
    _count += a;
    notifyListeners();
  }

  int get count => _count;
}
Copy the code

Monitor separately:

class BaseScopedPateRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopedModel<_BaseModel>(
      model: _BaseModel(),
      child: ScopedModel<_BaseModel2>(
        model: _BaseModel2(),
        child: _body(),
      ),
    );
  }

  static String get routeName => 'BaseScopedPate';

  Widget _body() => BaseScopedPate();
}
Copy the code

Show it where you need it. This is just the key code. For the full code, see the link at the bottom of the article.

ScopedModelDescendant<_BaseModel>(
        builder: (context, child, model) {
          _build += '_BaseModel build';

          return Center(
            child: Text('${model.count}')); }, child: Text('External Widgets'),
      ),
      ScopedModelDescendant<_BaseModel2>(
        builder: (context, child, model) {
          _build += '_BaseModel2 build';

          return Center(
            child: Text('${model.count}')); }, child: Text('External Widgets'),),Copy the code

We use P1 build to indicate that the whole page is refreshed once, B1 to indicate that control B is refreshed once, b2 to indicate that B2 is refreshed once, and several times is refreshed.

Take a look at the final result:

Why does p1 always come after B1 and b2?

I’ll leave you with a question to think about, and the answer will be published next time.

Full code view: Github view

Series of articles:

The article summary

Dart asynchrony and multithreading

Insight into state management –ScopeModel

Learn more about Flutter management –Redux

Detailed explanation of Flutter (iii. In-depth understanding of state management –Provider)

4. Deeper understanding of state management –BLoC

A detailed explanation of Flutter

1, Learn about the Stream

7. Understand the principle of drawing deeply

Detailed explanation of Flutter

Project recommend

  • A cool loading animation library
  • Flutter100 + component usage examples
  • Flutter entry to advanced ebook

The public,