Flutter's Random Talk: the implementation of component life cycle, State state management and local redrawing (Inherit)

Posted by dhrubajyoti on Sat, 29 Jan 2022 10:37:40 +0100

 

 

catalogue

life cycle

How does the component refresh when the State changes

InheritedWidget

InheritedModel

InheritedNotifier

Notifier

 

 

life cycle

There are actually two kinds of life cycles of a fluent: stateful widget and stateless widget.

These two are the two basic components of fluent, and their names have well indicated the functions of these two components: stateful and stateless.

 

(1)StatelessWidget

StatelessWidget is a stateless component. Its life cycle is very simple. There is only one build, as follows:

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ...;
  }
}

For stateless widget, it is rendered only once, and then it will not change any more.

 

Because there is only one build phase in the execution process of stateless components, only one build function will be executed during the execution, and there are no other life cycle functions, stateless components are better than stateful components in execution speed and efficiency. Therefore, when designing components, we should consider the business situation and try to use stateless components.

 

(2)StatefulWidget

StatelessWidget is a stateful component. The life cycle we discuss also basically refers to its cycle, as shown in the figure:

 

It includes the following stages:

  • createState 

    This function is the method of creating State in StatefulWidget. When StatefulWidget is called, createState will be executed immediately.

     

  • initState 

    This function is called for State initialization, so it can execute the initial assignment of State variables during this period. At the same time, it can interact with the server side during this period. After obtaining the server data, it calls setState to set up State.

     

  • didChangeDependencies

    This function refers to the global State when the State that the component depends on changes, such as language or theme, which is similar to the State stored in the front-end Redux.

     

  • build 
    It mainly returns the Widget that needs to be rendered. Since build will be called many times, only the logic related to returning the Widget can be done in this function to avoid abnormal state due to multiple executions. Pay attention to the performance problems here.

     

  • reassemble

    It is mainly used in the development stage. In the debug mode, this function will be called for each hot overload. Therefore, some debug code can be added during the debug stage to check code problems.

     

  • didUpdateWidget

    This function is mainly used when the component is rebuilt, such as hot overload, and the parent component is built, the method of the child component will be called. Secondly, after the method is called, the build method in this component will be called.

     

  • deactivate

    The component will be called after the node is removed. If the component is removed from the node and then not inserted into other nodes, it will continue to call dispose to permanently remove.

     

  • dispose

    Permanently remove components and release component resources.

     

In StatelessWidget, as long as we call setState, redrawing will be performed, that is, the build function will be re executed, so that the ui can be changed.

How does the component refresh when the State changes

Let's take a look at the code below:

class MyHomePage extends StatefulWidget {


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

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            WidgetA(_counter),
            WidgetB(),
            WidgetC(_incrementCounter)
          ],
        ),
      ),
    );
  }
}

class WidgetA extends StatelessWidget {
  final int counter;

  WidgetA(this.counter);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(counter.toString()),
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('I am a widget that will not be rebuilt.');
  }
}

class WidgetC extends StatelessWidget {
  final void Function() incrementCounter;

  WidgetC(this.incrementCounter);

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        incrementCounter();
      },
      child: Icon(Icons.add),
    );
  }
}

We have three widgets, one is responsible for displaying count, one button changes count, and the other is statically displaying text. These three widgets are used to compare the refresh logic of the page.

 

In the above code, the three widgets are_ Created in the build of MyHomePageState. After execution, click the button to find that all three widgets have been refreshed.

 

Select Track Widget Rebuilds on the shutter performance panel to see

Although the three widgets are stateless statelesswidgets, because_ When the State of MyHomePageState changes, the build function will be re executed, so the three widgets will be re created, which is why although Widget A is stateless, statelesswidgets can still be dynamically changed.

 

Therefore: a stateless StatelessWidget cannot be changed dynamically, but it cannot be changed internally through the State, but its construction parameters can be changed when the State of its parent Widget is changed. In fact, it can not be changed, because it is a new instance.

 

Next, we will create three components in advance, which can be used in_ MyHomePageState is created in the constructor. The modified code is as follows:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  List<Widget> children;

  _MyHomePageState(){
    children = [
      WidgetA(_counter),
      WidgetB(),
      WidgetC(_incrementCounter)
    ];
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: children,
        ),
      ),
    );
  }
}

 

Execute again, and it is found that clicking has no effect. You can see that there is no Widget refresh on the fluent performance (here refers to three widgets, of course, Scaffold is refreshed).

 

This is because the components were created in advance, so the three widgets were not re created when the build was executed, so the content displayed by Widget a did not change, because its counter was not re passed in.

 

Therefore, components that do not need dynamic changes can be created in advance and can be used directly during build, while components that need dynamic changes can be created in real time.

 

In this way, can local refresh be realized? We continue to change the code as follows:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  Widget b = WidgetB();
  Widget c ;

  _MyHomePageState(){
    c = WidgetC(_incrementCounter);
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            WidgetA(_counter),
            b,
            c
          ],
        ),
      ),
    );
  }
}

 

We only recreate WidgetB and WidgetC, while WidgetA is created in build. After execution, click the button WidgetA to change the content. Check the shutter performance and you can see that only WidgetA is refreshed, but WidgetB and WidgetC are not refreshed.

Therefore: by creating static components in advance, they can be used directly during build, while dynamic widgets can be created directly during build. This way can realize local refresh.

 

be careful:

As long as setState_ MyHomePageState will refresh, so widget a will refresh with it, even if the count has not changed. For example, in the above code, the_ Comment out the count + + code and click the button. Although the content has not changed, the widget A is still refreshed.

This situation can be optimized through InheritedWidget.

 

 

InheritedWidget

What is the function of InheritedWidget? On the Internet, some say it is data sharing, others say it is used for local refresh. Let's look at the official description:

Base class for widgets that efficiently propagate information down the tree.

It can be seen that its function is to effectively transfer messages from top to bottom of the Widget tree, so many people understand it as data sharing, but pay attention to this "effective", which is the key, and this effective is actually to solve the problems mentioned above.

 

So how does it work?

First create a class that inherits from InheritedWidget:

class MyInheriteWidget extends InheritedWidget{
  final int count;
  MyInheriteWidget({@required this.count, Widget child}) : super(child: child);

  static MyInheriteWidget of(BuildContext context){
    return context.dependOnInheritedWidgetOfExactType<MyInheriteWidget>();
  }

  @override
  bool updateShouldNotify(MyInheriteWidget oldWidget) {
    return oldWidget.count != count;
  }
}

 

Here, count is passed in. It is important to realize the updateShouldNotify function. The name of this function determines whether the Child Widget of inheritedwidwidget needs to be refreshed. Here, we judge that it can only be refreshed if it is changed from the previous one. This solves the problems mentioned above.

 

Then we need to implement a static of method to get the InheritedWidget from the Child Widget, so that we can access its count attribute. This is message passing, the so-called data sharing (because the child of the InheritedWidget can be a layout with multiple widgets, and these widgets can use the data in the InheritedWidget).

 

Then let's transform WidgetA:

class WidgetA extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    final MyInheriteWidget myInheriteWidget = MyInheriteWidget.of(context);
    return Center(
      child: Text(myInheriteWidget.count.toString()),
    );
  }
}

 

This time, you don't need to pass count in the constructor. You can directly get MyInheriteWidget through of and use its count.

 

Final modification_ MyHomePageState:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  Widget a = WidgetA();
  Widget b = WidgetB();
  Widget c ;

  _MyHomePageState(){
    c = WidgetC(_incrementCounter);
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            MyInheriteWidget(
              count: _counter,
              child: a,
            ),
            b,
            c
          ],
        ),
      ),
    );
  }
}

 

Note that here, MyInheriteWidget is used to wrap WidgetA, and WidgetA must be created in advance. If it is created in build, MyInheriteWidget will be refreshed every time it is refreshed, so the effect of updateShouldNotify function cannot be achieved.

 

Execute, click the button, and you can find that only widget a has been refreshed (of course, MyInheriteWidget has also been refreshed). If you comment out the in setState_ Count + + code, execute and click again. It is found that although MyInheriteWidget is refreshed, widget A is not refreshed because the count of MyInheriteWidget has not changed.

 

Now let's change the code and put widget B and C into MyInheriteWidget?

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            MyInheriteWidget(
              count: _counter,
              child: Column(
                children: [
                  a,
                  b,
                  c
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

 

The child of MyInheriteWidget is a Column. Put a, B and C under it. After execution, you will find that widget A is still refreshed, and neither B nor C is refreshed. This is because if the of function of myinheritedwidget is not executed in B and C, the dependOnInheritedWidgetOfExactType is not executed. In this way, there is no dependency, and myinheritedwidget will not notify them.

 

If we modify WidgetC, add a line myinheritewidget. In the build function of(context); Then, although there is no use, it can still be refreshed, because the dependency will be notified.

 

InheritedWidget will solve the redundant refresh problem. For example, there are multiple attributes in a page, and there are also multiple widgets to use these attributes, but not every Widget uses all attributes. If the most common implementation is used, these widgets need to be refreshed every time setState (no matter which attribute is changed). However, if we use multiple inheritedwidgets to classify these widgets, wrap them with the same InheritedWidget with the same attribute, and implement updateShouldNotify. In this way, when one attribute is changed, only the InheritedWidget related to the attribute will refresh its child, which improves the performance.

 

 

InheritedModel

InheritedModel inherits from InheritedWidget and expands its functions, so its functions are more powerful. Where exactly?

 

We know from the above that the InheritedWidget can decide whether to refresh the child by judging whether its data changes, but in fact, the data can be multiple variables or a complex object, and the child is not a single widget, but a series of widget combinations. For example, if you display a book, the data may include book name, serial number, date, etc., but each data may change separately. If you use InheritedWidget, you need an InheritedWidget class for each data, and then use the widget packaging of the data, so that other widgets will not refresh when you change a data.

 

However, the problem is that the widget hierarchy is more complex and chaotic. InheritedModel can solve this problem. The biggest function of InheritedModel is to refresh different widgets according to the changes of different data. Let's see how to implement it.

 

First create an InheritedModel:

class MyInheriteModel extends InheritedModel<String>{
  final int count1;
  final int count2;
  MyInheriteModel({@required this.count1, @required this.count2, Widget child}) : super(child: child);

  static MyInheriteModel of(BuildContext context, String aspect){
    return InheritedModel.inheritFrom(context, aspect: aspect);
  }

  @override
  bool updateShouldNotify(MyInheriteModel oldWidget) {
    return count1 != oldWidget.count1 || count2 != oldWidget.count2;
  }

  @override
  bool updateShouldNotifyDependent(MyInheriteModel oldWidget, Set<String> dependencies) {
    return (count1 != oldWidget.count1 && dependencies.contains("count1")) ||
        (count2 != oldWidget.count2 && dependencies.contains("count2"));
  }
}

 

Here we pass in two count s. In addition to implementing the updateShouldNotify method, we also need to implement the updateShouldNotifyDependent method. This function is the key. You can see that after we judge whether a certain data changes, we also judge whether there is a keyword in the dependencies:

count1 != oldWidget.count1 && dependencies.contains("count1")

What is this keyword? Where do you come from? As will be mentioned later, here is an impression.

 

Then you also need to implement a static of function to obtain the InheritedModel. The difference is that the code obtained here has changed:

InheritedModel.inheritFrom(context, aspect: aspect);

The aspect here is the keyword used later, and inheritFrom will put this keyword into dependencies for updateShouldNotifyDependent to use. The full function of this aspect will be explained in detail later.

 

Then we transform WidgetA:

class WidgetA extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    final MyInheriteModel myInheriteModel = MyInheriteModel.of(context, "count1");
    return Center(
      child: Text(myInheriteModel.count1.toString()),
    );
  }
}

 

As you can see, an aspect is defined here.

 

Then, because there are two counts, we add two widgets to handle count2:

class WidgetD extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    final MyInheriteModel myInheriteModel = MyInheriteModel.of(context, "count2");
    return Center(
      child: Text(myInheriteModel.count2.toString()),
    );
  }
}

class WidgetE extends StatelessWidget {
  final void Function() incrementCounter;

  WidgetE(this.incrementCounter);

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        incrementCounter();
      },
      child: Icon(Icons.add),
    );
  }
}

 

Here you can see that the aspect of WidgetD is different from that of WidgetA.

 

Final modification_ MyHomePageState:

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  int _counter2 = 0;
  Widget a = Row(
    children: [
      WidgetA(),
      WidgetD()
    ],
  );
  Widget b = WidgetB();
  Widget c ;
  Widget e ;

  _MyHomePageState(){
    c = WidgetC(_incrementCounter);
    e = WidgetE(_incrementCounter2);
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _incrementCounter2() {
    setState(() {
      _counter2++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            MyInheriteModel(
              count1: _counter,
              count2: _counter2,
              child: a,
            ),
            b,
            c,
            e
          ],
        ),
      ),
    );
  }
}

 

Widgets D and E handle count2, while A and C handle count. The child of MyInheriteModel is not A single Widget, but A Row, including Widget D and A.

 

After executing the code, you can find that when you click WidgetC, only WidgetA is refreshed (of course, MyInheriteModel is also refreshed); When you click widget D, only widget e refreshes. In this way, we have realized the local refresh in MyInheriteModel.

 

In fact, the principle is very simple. An aspect is equivalent to a tag when we pass the inheritedmodel inheritFrom(context, aspect: aspect); When obtaining MyInheriteModel, this Widget is actually dependent on MyInheriteModel, and this Widget is marked. At this time, if the data changes, when traversing all its dependencies, it will obtain its corresponding tag set dependencies through each dependent Widget, and then trigger updateShouldNotifyDependent to judge whether the Widget is refreshed.

 

Therefore, there is a map in InheritedModel (actually InheritedElement), which records the dependencies corresponding to each dependent Widget. Therefore, a Widget can have multiple tags, because dependencies is a Set, so it can respond to the changes of multiple data (for example, multiple data form a String as text display).

 

In fact, the above can be implemented with two inheritedwidgets, but the more complex the layout is, the more inheritedwidgets are required, and the maintenance is also time-consuming and laborious.

Therefore, we can see that InheritedModel is more flexible and powerful, and is more suitable for complex data and layout. By subdividing and refining each refresh area, only the smallest area is updated each time, which greatly improves the performance.

 

 

InheritedNotifier

 

 

Notifier

 

Topics: Flutter state