This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

Managing the Flutter Application State With InheritedWidgets. The website documentation or recommended articles will give you a better understanding of the status management mechanism of Flutter.

preface

Part I Introduction and Combat (41) : You can use a custom InheritedWidget subclass Called ModelBinding to enable child components to access state directly, eliminating the need to pass state along the component tree and reducing the coupling between parent and child components. But there are two problems:

  • ModelBindingClasses are not universal; each page needs to define its ownInheritedWidgetSubclasses.
  • The state change callback is still a component that needs to be passed from the top level to the operational state; the coupling is not completely uncoupled.

This article will modify the previous ModelBinding class to implement a more general, less coupled ModelBinding class.

Decouple the status update callback

The InheritedWidget version simplifies updating the model by binding components to the Model. Now, any subordinate component of the ModelBinding can take the model and update it, so that it does not need to be processed through a callback. As mentioned earlier, through viewModel.of (context), a child of a ModelBinding can get the value of the model, enabling model-dependent components to automatically update with changes to the model. Similarly, a child component of the ModelBinding can update model data via viewModel.update (context, newModel). It looks good!

To enable the static viewModel.update method to update the model, we need to introduce an additional stateful component. This is going to be a little bit more complicated.

  • willModelBindingBecomes a stateful component and holds the current state model object.
  • forModelBindingBuilt a_ModelBindingScopeInheritedWidgetA child component that referencesState<ModelBinding>– that is,_ModelBingingState“Refers to the parent state.
  • In order to changeModelBindingThe value of the current model, thus in the callsetStateMethods reconstructionModelBindingAnd rebuild the subordinate_ModelBindingScope.
  • through_ModelBindingScopeTo implement theViewModelClass to obtain model objects and update objects statically

The code looks like this:

static ViewModel of(BuildContext context) {
  _ModelBindingScope scope =
      context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
  return scope.modelBindingState.currentModel;
}

static void update(BuildContext context, ViewModel newModel) {
  _ModelBindingScope scope =
      context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
  scope.modelBindingState.updateModel(newModel);
}
Copy the code

Now that any child component of the ModelBinding can use these methods to update the model data, the button code below both retries and updates the model data.

Widget build(BuildContext context) {
  return ElevatedButton(
    child: Text('Hello World ${ViewModel.of(context).value}'),
    onPressed: () {
      ViewModel model = ViewModel.of(context);
      ViewModel.update(context, ViewModel(value: model.value + 1)); }); }Copy the code

Run it and it looks fine (full code: model_binding_v1.dart). Should we celebrate? However, if we had hundreds of states we would write dozens or hundreds of ModelBinding classes, and each Model would provide a static of(context) and update method, right?

Build a generic ModelBinding class using generics

To ensure universality of the ModelBinding, we need to use generics to dynamically bind the state model objects. To start with, change the _ModelBIndingScope class:

class _ModelBindingScope<T> extends InheritedWidget {
  _ModelBindingScope({
    Key key,
    @required this.modelBindingState,
    Widget child,
  })  : assert(modelBindingState ! =null),
        super(key: key, child: child);

  final _ModelBindingV2State<T> modelBindingState;

  @override
  bool updateShouldNotify(_ModelBindingScope oldWidget) => true;
}
Copy the code

It’s easy to change, just add generic parameters. Next up is _ModelBindV2State, which may be changed to generic.

class _ModelBindingV2State<T> extends State<ModelBindingV2<T>> {
  T currentModel;
  @override
  void initState() {
    super.initState();
    currentModel = widget.create();
  }

  void updateModel(T newModel) {
    if (currentModel != newModel) {
      setState(() {
        currentModel = newModel;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return _ModelBindingScope<T>(
      modelBindingState: this, child: widget.child, ); }}Copy the code

In fact, it just adds the generic parameter. The one thing to note here is that we built the initial state directly in _ModelBidingV2State. Now, because it’s generic, we can’t build the generic object directly, so we need to get it from the stateful component. Here we call the Create method of the ModeBinding class in initState to return a generic object (or, of course, we can use assignment directly, depending on how the ModelBinding class gets the initial state object).

In this case, we need to pass a generic object fetching method, create, to the constructor. In addition, we need to promote the fetching model object methods and updating methods that were previously placed in the ViewModel to the ModelBinding class. And become a generic method, so that the external only need the ModelBinding class can complete the acquisition and update of the state model object, simplify the implementation of the state model object to the greatest extent.

class ModelBindingV2<T> extends StatefulWidget {
  ModelBindingV2({Key key, @required this.create, this.child})
      : assert(create ! =null),
        super(key: key);
  final ValueGetter<T> create;
  final Widget child;

  @override
  _ModelBindingV2State<T> createState() => _ModelBindingV2State<T>();

  static T of<T>(BuildContext context) {
    _ModelBindingScope<T> scope =
        context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
    return scope.modelBindingState.currentModel;
  }

  static voidupdate<T>(BuildContext context, T newModel) { _ModelBindingScope<T> scope = context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope); scope.modelBindingState.updateModel(newModel); }}Copy the code

After the modification, our actual Controller code will look like this:

class StateViewControllerV2 extends StatelessWidget {
  StateViewControllerV2({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Model Binding Generic Edition'), ), body: Center( child: ModelBindingV2( create: () => ViewModel(), child: ViewController(), ), ), ); }}class ViewController extends StatelessWidget {
  const ViewController({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      child: Text('Hello World ${ModelBindingV2.of<ViewModel>(context).value}'),
      onPressed: () {
        ViewModel model = ModelBindingV2.of<ViewModel>(context);
        ModelBindingV2.update(context, ViewModel(value: model.value + 1)); }); }}Copy the code

As you can see, the entire ModelBinding class does the fetching and updating of state and applies to any state model class.

conclusion

This code has been uploaded to: Status Management Example. From the previous article and this one, the core component of state management is actually an InheritedWidget. With the ability to use InheritedWidget to update all the child components that depend on that component’s state when their state changes. Through the use of generics, we have built one of the simplest common state management components. Of course, state management in the real world is much more complex than this, but understanding how it works will help you optimize performance.


I’m an island user with the same name on wechat. This is a column on introduction and practice of Flutter.

👍🏻 : feel a harvest please point to encourage!

🌟 : Collect articles, convenient to read back!

💬 : Comment exchange, mutual progress!