Lizhaoxuan.github. IO

preface

As for Fultter, there are usually multiple controls that can do the same thing, and too many choices can always lead to more or less choice confusion and performance concerns.

When you first approach Flutter, you must first face two big problems: the StatelessWidget and the StatefulWidget. The majority of people explained the choice between the two controls as follows: “As their name suggests, stateless static views use the StatelessWidget, while interactive, dynamic ones use the StatefulWidget.”

This explanation is correct, but it is too vague. It seems that StatefulWidget can be used everywhere StatelessWidget appears. Therefore, misuse of StatefulWidget becomes an easy thing to happen for possible later changes and for convenience of coding.

So today we’ll talk more about the differences and use of StatefulWidget and StatelessWidget.

StatefulWidget differs from StatelessWidget

The general existence of vague explanations, if you want to mock it, is not wrong, but it does produce some non-solutions.

My personal understanding of StatefulWidget and StatelessWidget:

Once a StatelessWidget is initialized, it cannot be changed. If you want to change it, you need to create a new StatelessWidget to replace it. But because StatelessWidget is static, it has no way to recreate itself. So StatefulWidget provides a mechanism to mark itself as dirty by calling setState((){}), waiting for the next system redraw check.

StatefulWidget dynamic cost

By definition, StatefulWidget is a universal thing, but I expect you to get a sense of what it takes to make a StatefulWidget dynamic:

Calling setState((){}) in the State class to update the view will trigger state.build! It also indirectly triggers the constructor and build methods for each of its child widgets.

What does that mean? If your root layout is a StatefulWidget, every call to setState((){}) in the root State will be a page-wide rebuild of all the widgets!! Here’s an example:

class MyStatefulWidget extends StatefulWidget { @override State<StatefulWidget> createState() { // TODO: implement createState return null; } } class CustomerState extends State<MyStatefulWidget> { int _num = 0; @override Widget build(BuildContext context) { // TODO: implement build return Row(children: <Widget>[ GestureDetector( onTap: () { setState(() { _num++; }); }, child: Text("Click My"), ), Text("1:AAAAA"), Text("2:BBBBB"), Text("3:C:" + _num.toString()), CustomerContainer() ]); } } class CustomerContainer extends StatelessWidget { @override Widget build(BuildContext context) { for (int i = 0; i < 1000000; I++) {print(" I am a time operation for:" + i.tostring ()); } return Container( child: Text("4:DDDD"), ); }}Copy the code

For the code above, every time “My Click” is clicked, the CustomerState Build method, and the Row, Text, CustomerContainer and other sub-widgets are rebuilt. It is not clear whether the Flutter will be cache optimized when drawn. But a lot of object creation and method execution are unavoidable. If a child Widget’s build or build does something time-consuming, that’s a disaster!!

So you can understand why creating a Flutter project root layout is a StatelessWidget.

How does StatefulWidget implement interface updates?

setState(() {
	_num++;
});
Copy the code

When it comes to learning a new technology, the old technology can be scary.

SetState ((){}) should be implemented in a similar way to Android DataBinding or Vue data hijacking, implementing observer mode and performing targeted updates. Only widgets bound to _num are updated locally.

With this in mind, there is no psychological burden to use statefulWidgets in large numbers.

But the above case has been very straightforward to tell us that the fact is not so!!

SetState ((){});

@protected
  void setState(VoidCallback fn) {
  	...
  	_element.markNeedsBuild();
  }
Copy the code

Omitting all the assert effects, the only sentence that actually makes sense is this one, marking Element as the required build state. Look further down:

void markNeedsBuild() {
	...
    if (dirty)
      return;
    _dirty = true;
    owner.scheduleBuildFor(this);
}
Copy the code

Mark Element as dirty and execute owner’s scheduleBuildFor method. The owner is a BuildOwner.

void scheduleBuildFor(Element element) { ... if (! _scheduledFlushDirtyElements && onBuildScheduled ! = null) { _scheduledFlushDirtyElements = true; onBuildScheduled(); }... }Copy the code

OnBuildScheduled () calls ensureVisualUpdate() and scheduleFrame(), so scheduleFrame

void scheduleFrame() { if (_hasScheduledFrame || ! _framesEnabled) return; ui.window.scheduleFrame(); _hasScheduledFrame = true; }Copy the code

The scheduleFrame() method of the Window class is called. ScheduleFrame () is a native method that does the actual drawing of the interface. At this point we basically know what we need to know.

Flutter does not bind data in both ways. It doesn’t matter what code you write in state.setState ((){}). It only flags that the State object needs to be rebuilt.

setState(() {
	_num++;
});
Copy the code
_num++;
setState(() {});
Copy the code

So either way you can implement Widget updates that depend on _num.

How do I choose between StatefulWidget and StatelessWidget in development?

From the three summaries above, you should get an idea of how simple and expensive view updates for StatefulWidget can be.

In contrast to Vue, which uses bidirectional data binding to enable local DOM updates to improve efficiency, Flutter offloads some of the performance optimizations that the framework would otherwise be responsible for onto the developer. There is a bit of memory reclamation similar to C++ and Java.

Since you can’t resist, lie down and enjoy the “freedom”. Let’s talk about how to choose between StatefulWidgets and StatelessWidgets in development to improve view update performance.

Let’s start with some decision points:

  • Preferentially use the StatelessWidget
  • Use StatefulWidget with large widgets (such as root layout and secondary root layout) with caution
  • Use statefulWidgets on leaf nodes whenever possible
  • Will be called tosetState((){})The code is as small as possible and the updated view is encapsulated in a module as small as possible.
  • If a Widget needs reBuild, it should have as few children, siblings, and siblings as possible

There are other things to be aware of

  • Compared to Android’s View, the constructor of the Flutter Widget may be executed many times and should do as little as possible
  • The Flutter Widget build method may be executed multiple times and should do as little as possible

Assuming you have the Widget tree above, red represents a Widget that will be changed. If you follow this layout structure, then every time the red Leaf node changes and is rebuilt, its four siblings are also rebuilt. For this structure, you should optimize as follows:

Encapsulate the changing node into a smaller branch with as few siblings as possible.

Let’s use a simple demo to illustrate: BBB is static copy, and every time you Click My, the number after AAA increases by 1

class CustomerStatefulWidget extends StatefulWidget { final String _name; CustomerStatefulWidget(this._name); @override State<StatefulWidget> createState() { print("TAG, CustomerStatefulWidget:" + _name + " build"); return CustomerState("CustomerStateA"); } } class CustomerState extends State<CustomerStatefulWidget> { String _name; CustomerState(this._name) {print("TAG, CustomerState:" + _name + "construct "); } int _customerStatelessText = 0; @override Widget build(BuildContext context) { print("TAG, " + _name + " build"); return Container( margin: EdgeInsets.only(top: 100), color: Colors.yellow, child: Column( children: <Widget>[ CustomerStatelessWidget("BBB", "BBB"), CustomerStatelessWidget( "AAA", "AAA:" + _customerStatelessText.toString()), GestureDetector( onTap: () { print("Click My"); setState(() { _customerStatelessText++; }); }, child: Text("Click My"), ) ], ), ); } } class CustomerStatelessWidget extends StatelessWidget { final String _text; final String _name; CustomerStatelessWidget(this._name, this._text) {print("TAG, CustomerStatelessWidget:" + _name + "construct "); } @override Widget build(BuildContext context) { print("TAG, CustomerStatelessWidget:" + _name + " build"); if (_name == "BBB") { // for (int i = 0; i < 10000000; i++) { // print("for:" + i.toString()); } print(" I am a time method, time 2s"); } return Text(_text); }}Copy the code

After we Click My, take a look at the log:

I/flutter (31310): Click My I/flutter (31310): TAG, CustomerStateA build I/flutter (31310): TAG, CustomerStatelessWidget: BBB structure I/flutter (31310) : the TAG, CustomerStatelessWidget: triple structure I/flutter (31310) : TAG, CustomerStatelessWidget: BBB build I/flutter (31310) : I am a time-consuming method, takes 2 s I/flutter (31310) : TAG, CustomerStatelessWidget:AAA buildCopy the code

BBB was originally static without Rebuild, because it is a sibling of AAA, and it passively redraws when AAA changes. Worse, BBB also has a very time-consuming build method. So how do you optimize?

Encapsulate ClickMy and AAA controls in a smaller StatefulWidget, BBB up to the StatelessWidget

class WrapStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { print("TAG, WrapStatelessWidget: build"); return Container( margin: EdgeInsets.only(top: 100), color: Colors.yellow, child: Column( children: <Widget>[ CustomerStatelessWidget("BBB", "BBB"), CustomerStatefulWidget("AAA") ], ), ); } } class CustomerStatefulWidget extends StatefulWidget { final String _name; CustomerStatefulWidget(this._name); @override State<StatefulWidget> createState() { print("TAG, CustomerStatefulWidget:" + _name + " build"); return CustomerState("CustomerStateA"); } } class CustomerState extends State<CustomerStatefulWidget> { String _name; CustomerState(this._name) {print("TAG, "+ _name +" construct "); CustomerState(this._name) {print("TAG, "+ _name +" construct "); } int _customerStatelessText = 0; @override Widget build(BuildContext context) { print("TAG, CustomerState:" + _name + " build"); return Container( child: Column( children: <Widget>[ CustomerStatelessWidget( "AAA", "AAA:" + _customerStatelessText.toString()), GestureDetector( onTap: () { print("Click My"); _customerStatelessText++; setState(() {}); }, child: Text("Click My"), ) ], ), ); } } class CustomerStatelessWidget extends StatelessWidget { final String _text; final String _name; CustomerStatelessWidget(this._name, this._text) {print("TAG, CustomerStatelessWidget:" + _name + "construct "); } @override Widget build(BuildContext context) { print("TAG, CustomerStatelessWidget:" + _name + " build"); if (_name == "BBB") { // for (int i = 0; i < 1000000; i++) { // print("for:" + i.toString()); } print(" I am a time method, time 2s"); } return Text(_text); }Copy the code

Let’s click ClickMy again to see the log:

I/flutter (31310): Click My I/flutter (31310): TAG,CustomerStateA build I/flutter (31310): TAG, CustomerStatelessWidget: AAA structure I/flutter (31310) : the TAG, CustomerStatelessWidget: AAA buildCopy the code

AAA redraws no longer force BBBS to redraw!

conclusion

To reiterate the decision points used by StatefulWidget:

  • Preferentially use the StatelessWidget
  • Use StatefulWidget with large widgets (such as root layout and secondary root layout) with caution
  • Use statefulWidgets on leaf nodes whenever possible
  • Will be called tosetState((){})The code is as small as possible and the updated view is encapsulated in a module as small as possible.
  • If a Widget needs reBuild, it should have as few children, siblings, and siblings as possible

There are other things to be aware of

  • Compared to Android’s View, the constructor of the Flutter Widget may be executed many times and should do as little as possible
  • The Flutter Widget build method may be executed multiple times and should do as little as possible

If your code has a lot of StatefulWidgets, refactor them