This is the fourth 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

Generally speaking, interactive applications can be divided into three parts: Model, View and Controller, also known as THE MVC pattern. Anyone who has used the Flutter sample will be familiar with the reactive way in which widgets and callbacks are used to build views and controllers. However, for the Model layer, this is not so clear. The Model layer of the Flutter actually represents the state in which it remains. Widgets provide a visual representation of state and allow users to modify it. When the widget’s build method retrieves values from the Model, or when the callback function changes the Model value, the widget is rebuilt as the Model changes. This article is about how that happened. This article reviews how the Stateful components of Flutter and the InheritedWidget classes bind visual elements of the application to the Model. It also introduces a ModelBinding class that can be easily incorporated into your application.

The statement

This is not the only way to build AN MVC application. If you’re building a large application, there are a number of ways to attach the Flutter to a model. Some of them are listed at the end of this article. In other words, even if you decide not to use the ModelBinding class in the end, you can still gain the state management mechanism of Flutter.

This article is not for beginners, you need to have at least some understanding of the Flutter API. For example:

  • You can write classes proficiently using Dart and understand= =Operator and hash code overloading, and generic methods.
  • Familiar with the basic Flutter component classes and know how to write a new component yourself.

Applied model

To illustrate this example, we need a sample application model. In order to focus, we will make this model as simple as possible. There is only one value in our model class, and the operator == and hashCode overloading are included.

class ViewModel {
  const ViewModel({ this.value = 0 });

  final int value;

  @override
  bool operator= = (Object other) {
    if (identical(this, other))
      return true;
    if(other.runtimeType ! = runtimeType)return false;
    final ViewModel otherModel = other;
    return otherModel.value == value;
  }

  @override
  int get hashCode => value.hashCode;

  static ViewModel of(BuildContext context) {
    final ModelBinding binding = context.dependOnInheritedWidgetOfExactType(aspect: ModelBinding);
    returnbinding.model; }}Copy the code

Of course, this model can also be extended according to the actual situation of the application. Note that this is an immutable model, so the only thing to change is to replace it. The following MVC approach could also use a mutable model, but that would be a little more complicated.

Bind the model to a stateful component

This is the simplest way to integrate the model, and is ideal for applications that take an afternoon to complete. A stateful component is associated with a State object that preserves the State. The build method of the State object will build the tree of child components of the component, just like the build method of stateless components. When the setState method of the State object is called, a component rebuild is triggered after an interval of time between display frame switches. If a stateful component’s state object holds the model, then the build method used to configure it uses the model’s value when it calls the setState method. The following stateful component is quite simple. It just holds a ViewModel object and provides an update method to update the model.

class ViewController extends StatefulWidget {
  _ViewControllerState createState() => _ViewControllerState();
}

class _ViewControllerState extends State<ViewController> {
  Model currentModel = ViewModel();
  
  void updateModel(ViewModel newModel) {
    if (newModel != currentModel) {
      setState(() {
        currentModel = newModel;
      });
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        updateModel(ViewModel(value: currentModel.value + 1));
      },
      child: Text('Hello World ${currentModel.value})); }}Copy the code

Restrictions on using stateful component bindings

Writing code in this way would be a fool (😂 that’s not what I’m saying, just wake up — our previous sample code was written in this way, and it was really rudimentary). For relatively large-scale applications, it is not applicable. Specifically, it has the following defects:

  • When the model changes, the wholeViewControllerAnd the entire component tree is rebuilt, not just the component that depends on the model (i.eText).
  • If a child component needs to use a model’s value, the model can only be passed as a constructor parameter or using a callback closure.
  • If components further down the hierarchy need to use the values of the model, it is only by passing model objects down the chain of the component tree.
  • If the child component needs to modify the value of the model, it must be calledViewControllerThe callback function passed. In this case it’s likeRaisedButtononPressedThat way.

Thus, stateful components are more suited to creating their own internal state than to sharing a data model in a complex application. To the programmer, it’s the complexity that makes us more valuable.

Version 0: Use the InheritedWidget binding model

There’s something special about the InheritedWidget class that makes it a good fit for sharing models in the component tree.

  • Given aBuildContextTo find the nearest of a particular typeInheritedWidgetAncestor node is very convenient, just need to search by table.
  • InheritedWidgetTheir dependencies, such as for access, are trackedInheritedWidgettheBuildContext. When aInheritedWidgetWhen it is rebuilt, all its dependent objects are rebuilt.

In fact, you’ve probably already encountered inheritedWidgets, such as the Theme component. The theme.of (context) method returns the ThemeData object for the Theme and treats the context as a dependent object for the Theme. If the Theme object is rebuilt and different ThemeData values are used, all components that depend on theme.of () are rebuilt automatically.

Using a custom InheritedWidget subclass allows you to implement the hosting of your application model in the same way. Here, we call this subclass ModelBinding because it associates the application components with the model.

class ModelBinding extends Inherited {
  ModelBinding({
    Key key,
    this.model = const ViewModel(),
    Widget child,
  }): assert(model ! =null), super(Key: key, child:child);
  
  final ViewModel model;
  
  @override
  boolupdateShouldNotify(ModelBinding oldWidget) => model ! = oldWidget.model; }Copy the code

The updateShouldNotify method is called when the ModelBinding is rebuilt. If the return value is true, all components that depend on it will be rebuilt.

BuildContext inheritFromWidgetOfExactType () method is used to find a InheritedWidget. Because it’s a little ugly, we’ll come back to it later. Normally, this method is wrapped using static methods. Adding a lookup method to the ViewModel makes the ViewModel object available to any child component that depends on the ModelBinding object through the model.of (context) method.

// Now the sub-components in the ModelBinding can be accessed through model.of (context)
Text('Hello WOrld ${ViewModel.of(context).value}')
Copy the code

Any child of a ModelBinding can do this without passing ViewModel objects through layers. If the ViewModel object changes, the child components are automatically rebuilt just like the ViewController.

The component where the ModelBinding resides must itself be a stateful component. In order to change the ViewModel object, the component still needs to call the setState method. Here we use a StateViewController stateful component to hold the Model object, and methods to update the Model object are passed to the ViewController as callback functions.

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

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

class _StateViewControllerState extends State<StateViewController> {
  ViewModel currentModel = ViewModel();

  void _updateModel(ViewModel newModel) {
    setState(() {
      currentModel = newModel;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Model binding version 0'), ), body: Center( child: ModelBinding( model: currentModel, child: ViewController(_updateModel), ), ), ); }}Copy the code

In this case, the ViewModel class is simply an immutable object, so you only need to assign a new ViewModel object replacement to complete the update. Replacing the object can also be more complex, for example if the object references objects that require lifecycle management, then replacing the model may require the destruction of some of the old objects.

A bug in this version

Click the button and the number automatically increases by 1. The code has been submitted to: Status Management Code. On the plus side, this version of the ModelBinding class makes it easy for components to acquire model objects and automatically rebuild them when the model changes.

However, this version, in turn, needs to be passed down the component tree using the updateModel callback method to the component that actually controls the state change, and the code is not easy to maintain. In the next version we will implement a more general ModelBinding class, so that child components can update ViewModel objects directly through the update method provided by ModelBinding.

conclusion

This article describes the MVC model used in the Flutter application. For Flutter, the model in the application is actually the state of the components. Passing state directly through layers to control the display of components at the lower level of the component tree can result in code coupling. For this reason, we introduce a ModelBinding class that InheritedWidget so that child components can directly access the state of their parent component, avoiding passing status parameters through layers. There is, of course, one drawback to this version, which we will retrofit to a more general ModelBinding class in the next installment, is that the callback method to change the state still needs to be passed along the component tree.


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!