Introduction to the

Scoped_model is a simple third-party state management framework. The source code of scoped_model is a DART file with very little code and uses InheritedWidget and AnimatedBuilder features to achieve state management. Official website: pub.flutter-io.cn/packages/sc…

How to install
  • Pubspec. Yaml:
Dependencies: scoped_model: ^ 1.0.1Copy the code
  • Run: flutter pub get
  • Dart file reference: import ‘package:scoped_model/scoped_model.dart’;
Code sample

Using the same +1 button example, use the scoped_model implementation:

import 'package:flutter/material.dart'; import 'package:scoped_model/scoped_model.dart'; class ScopedModelTest extends StatefulWidget { @override _ScopedModelTestState createState() => _ScopedModelTestState();  } class _ScopedModelTestState extends State<ScopedModelTest> {// Create model CountModel _model = CountModel(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("ScopedModel"), centerTitle: true, ), body: Center( child: ScopedModel<CountModel>( model: _model, child: CountWidget(),// To make the widget that uses the data in the Model a child of the ScopedModel),), floatingActionButton: floatingActionButton (child: Icon(Icons.add), onPressed: (){ _model.increment(); },),); }} // define the Model class CountModel extends Model{// Share the state data int _counter = 0; int get counter => _counter; Void increment(){++_counter; notifyListeners(); }} class CountWidget extends StatefulWidget {@override _CountWidgetState createState() => _CountWidgetState(); } class _CountWidgetState extends State<CountWidget> { @override Widget build(BuildContext context) { return Container( child: ScopedModelDescendant<CountModel>( builder: (context,child,model){ return Text(model.counter.toString()); },),); }}Copy the code

Resolution:

  • Custom CountModel inherits Model, member _counter is state data; The method increment() is defined, where changes are made to the status data, and notifyListeners() are called to tell all widgets dependent on the status data that the status data has been updated. Internally, the widget’s setState method is called to rebuild the page and render the new state data.
  • Custom CountWidget, which emulates a Widget that uses state data. Note the use of ScopedModelDescendant, which is a StatelessWidget that internally calls scopedModel. of(context, rebuildOnChange: RebuildOnChange) gets an instance of the generic CountModel in the render tree, passes it back through Builder, and then gets the state data defined in model through model.counter.
  • Using ScopedModel
ScopedModel<CountModel>(model: _model, child: CountWidget(),// To make the widget that uses the data in the model a child of the ScopedModel),Copy the code

It takes two arguments: model is the CountModel created earlier, which is where _counter is located, and is operated by floatingActionButton. A child is a Widget that depends on CountModel. The ScopedModel ends up passing the Model and child inside to an InheritedWidget, which uses the InheritedWidget’s features to pull the Model down from the render tree to CountWidget.

  • The state data in the Model are changed by _model.increment() in floatingActionButton to create notifyListeners().
Core principles
  • Data sharing: Is a InheritedWidget ScopedModel interior, CountWidget ScopedModel child, is actually a child InheritedWidget, the model is the Shared data in InheritedWidget, This allows CountWidget to retrieve the Model using InheritedWidget features to display the model’s data.
  • Cross-widget updates: The essence of a Model is a Listenable, with a Set listeners that are callbacks to all widgets that use the Model location, such as the ScopedModel here. AnimatedBuilder is a middleware component of ScopedModel, which is essentially an AnimatedWidget, which is a StatefulWidget, The addListener method of model is called inside initState with a method whose content is setState. The setState is called when state data is changed to notifyListeners within the Model, triggering a rebuild.
Source code analysis

Note that the source code listed here is an abridged version, with only the core content preserved

  • Model
Abstract class Model extends Listenable {set final set <VoidCallback> _listeners = set <VoidCallback>(); Override void addListener(VoidCallback Listener) {_Listeners. Add (listener); } /// increment calls this method inside CountModel to execute all callbacks. @protected void notifyListeners() { _listeners.toList().forEach((VoidCallback listener) => listener()); }}Copy the code
  • ScopedModel

Go to AnimatedBuilder –> AnimatedWidget _InheritedModel –> InheritedWidget

class ScopedModel<T extends Model> extends StatelessWidget { /// The [Model] to provide to [child] and its descendants. final T model; /// The [Widget] the [model] will be available to. final Widget child; ScopedModel({@required this.model, @required this.child}) : assert(model ! = null), assert(child ! = null); Override Widget build(BuildContext context) {override Widget build(BuildContext context) { --> AnimatedWidget return AnimatedBuilder( animation: model, builder: (context, _) => _InheritedModel<T>(model: model, child: child), ); }}Copy the code
  • AnimatedWidget state

Listenable is addListener in model initState and setState in parameter methods

abstract class AnimatedWidget extends StatefulWidget {
  const AnimatedWidget({
    Key key,
    @required this.listenable,
  }) : assert(listenable != null),
       super(key: key);

  final Listenable listenable;

  @override
  _AnimatedState createState() => _AnimatedState();

}

class _AnimatedState extends State<AnimatedWidget> {
  @override
  void initState() {
    super.initState();
    widget.listenable.addListener(_handleChange);
  }
  void _handleChange() {
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }

}

Copy the code
  • _InheritedModel

This is an InheritedWidget where the preceding Model and CountWidget are ultimately passed

class _InheritedModel<T extends Model> extends InheritedWidget { final T model; final int version; _InheritedModel({Key key, Widget child, T model}) : this.model = model, this.version = model._version, super(key: key, child: child); @override bool updateShouldNotify(_InheritedModel<T> oldWidget) => (oldWidget.version ! = version); }Copy the code
  • ScopedModelDescendant is a layer of encapsulation to retrieve shared models via scopedModel. of(context, rebuildOnChange: RebuildOnChange) gets the model and passes it through the Builder. The type of build is ScopedModelDescendantBuilder
class ScopedModelDescendant<T extends Model> extends StatelessWidget {
 
  @override
  Widget build(BuildContext context) {
    return builder(
      context,
      child,
      ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange),
    );
  }
}

typedef Widget ScopedModelDescendantBuilder<T extends Model>(
  BuildContext context,
  Widget child,
  T model,
);
Copy the code