In the world of Flutter, everything is a Widget. A Widget has two subclasses, StatelessWidget and StatefulWidget. Learn what these two types of widgets are, what they differ from each other, and how to choose between them.

Learn about the UI programming paradigm in Flutter before looking at statelessWidgets and StatefulWidgets.

UI becomes a paradigm

UI programming paradigm: How to adjust the presentation style of a control. There are two types of programming, imperative and declarative.

Imperative programming: Use commands to precisely control the properties of a control, such as Android, to modify the text content of TextView, textView.text = “newText”.

Declarative programming: The core design idea is to separate views from data. This declarative programming is used in Flutter. In addition to designing the Widget layout, you need to maintain a copywriting data set ahead of time and bind the Widget to the data set so that the Widget can render against that data set.

The advantage of declarative programming: When you need to change the text of a page, you only need to change the text in the dataset and inform the Flutter framework to trigger the widget to re-render. Instead of focusing on the details of the UI programming process, you just need to maintain the data set.

StatefulWidget

StatelessWidget: a StatelessWidget whose display style is determined at initialization and does not change thereafter. For example, Text, Container, Row, and Column.

Check out the source code for Text:

Class Text extends StatelessWidget {// Constructor extends StatelessWidget {// Constructor extends StatelessWidget {// Constructor extends StatelessWidget {// Constructor extends StatelessWidget {// Constructor extends StatelessWidget const Text(this.data, {Key Key, this.textAlign, this.textDirection, // Other parameters... }) : assert(data ! = null), textSpan = null, super(key: key); final String data; final TextAlign textAlign; final TextDirection textDirection; // Other attributes... @override Widget build(BuildContext context) { ... Widget result = RichText(// initialize configuration...) ); . return result; }}Copy the code

After the constructor assigns its property list, the build method returns the child RichText initialized by its property list (such as text data, alignment textAlign, text display direction textDirection, etc.). After that, the inside of the Text no longer responds to changes in the external data.

Usage scenario: You can use the StatelessWidget if the display style is determined at the beginning and does not respond to changes in the data. For example, the title of each page is initialized only once, followed by no change, and the dialog prompts error messages.

StatefulWidget

StatelessWidget: A stateful Widget that, at initialization, cannot determine the display style of the Widget and can then be rerendered in response to changes in the data set. For example, the user’s interactions (for example, the user clicking a button) or changes to its internal data (for example, network data backpacking) are handled and reflected in the UI.

Take a look at the source code for an example: Image

Class Image extends StatefulWidget {// Constructor and property declaration part const Image({Key Key, @required this. Image, // other parameters}) : assert(image ! = null), super(key: key); final ImageProvider image; // Other attributes... @override _ImageState createState() => _ImageState(); . } class _ImageState extends State<Image> { ImageInfo _imageInfo; // Other attributes... void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) { setState(() { _imageInfo = imageInfo; }); }... @override Widget build(BuildContext context) { final RawImage image = RawImage( image: _imageInfo? .image, // Other initial configuration...) ; return image; }... }Copy the code

First, you can see that there is no build method in the StatefulWidget, but a createState method. The createState method gives the job of building the view to _ImageState; In _ImageState, the view information is obtained via ImageInfo.

ImageInfo data changes, update the Image process:

  1. As the ImageInfo data changes, the State object listens for changes to the _imageInfo property through the _handleImageChanged method
  2. Call the setState method of the _ImageState class to inform the Flutter framework: “My data has changed. Please reload the image with the updated _imageInfo data!” .
  3. The Flutter framework marks the view state and updates the UI.

The StatefulWidget responds to changes in data and displays a static UI. Isn’t the StatelessWidget a bit redundant?

Take a look at the widget update mechanism:

Widgets are immutable, and updating means destroy + build. The StatelessWidget is static and does not need to be updated once created; For the StatefulWidget, calling the setState method in the State class to update the data triggers the destruction and rebuilding of the view and, indirectly, of each of its child widgets.Copy the code

This means that if our root layout is a StatefulWidget, every call to update the UI in its State will be a destruction and reconstruction of all the widgets for the entire page.

Instead of destroying the entire RenderObject tree rebuild, the Element layer inside Flutter minimizes modifications to the real rendered view and improves rendering efficiency. However, the destruction and reconstruction of a large number of Widget objects is inevitable. If the rebuilding of a child Widget involves some time-consuming operation, the rendering performance of the page will decrease dramatically.

Properly evaluating whether to use a StatefulWidget is the most direct way to improve Flutter rendering performance.

How to choose between StatelessWidget and StatefulWidget?

In a word: for its static presentation only, use the StatelessWidget, otherwise use the StatefulWidget.

The Widget’s life cycle

Widgets in Flutter also have a life cycle and are represented by State. (For StatefulWidget)

State lifecycle

The life cycle of a State refers to the stages of the process through which the Widget associated with the State is created, displayed, updated, stopped, and destroyed with user involvement.

As you can see from the figure, there are three main processes in the life cycle of a state: creation, update, and destruction.

create

State is initialized in sequence: constructor -> initState -> didChangeDependencies -> Build, followed by page rendering

Constructor: The starting point of the state life cycle, created by the stateFulWide get#createState method, where initialization data can be received from outside.

InitState, called when the State object is inserted into the view tree. This function will only be called once in the lifetime of State, so we can do some initialization here. Similar to the onCreate method of an Activity in Android.

DidChangeDependencies handle State object dependencies that are called by Flutter after initState() is called.

Build, which builds the view. After the above steps, the Framework decides that State is ready and calls Build. In this function, we need to create a Widget and return it based on the initial configuration data passed in by the parent Widget and the current State of State.

update

How is it updated?

There are three methods: setState, didchangeDependencies, and didUpdateWidget.

SetState: This method is used to inform the Flutter framework to update the UI when data changes.

DidchangeDependencies: State Calls back to this method when the dependencies of the object change, subsequently triggering a component build. Such as system language switch.

DidUpdateWidget: This function is called when the configuration of a Widget changes, for example, when the parent Widget triggers a rebuild (that is, when the state of the parent Widget changes), or when a hot overload occurs.

The destruction

When a component is removed or a page is destroyed, the system calls two methods, Deactivate and Dispose, to remove or dispose the component.

Deactivate: called when the component’s visible state changes.

Dispose: When State is permanently removed from the view tree, this is the stage where the component is destroyed, so we can do the final resource release, remove the listener, clean up the environment, and so on. Similar to the onDestory method of an Activity in Android.

The life cycle of a state is similar to that of an activity. The following uses a table to summarize, easy to compare and understand.

The method name function Call time Call the number
A constructor Receives data from the parent widget to initialize the UI Create the State 1
initState Pre-render related initialization work State is inserted into the view tree 1
didchangeDependencies Process when state-dependent objects change After initState or when the State dependency changes > = 1
build Build view State is ready for data rendering > = 1
setState Triggering view rebuild You need to update your UI > = 1
didchangeDependencies Handle configuration changes to widgets The parent widget setState triggers the child widget rebuild > = 1
deactivate Component removed Component not visible > = 1
dispose Component destroyed The component is removed forever 1

APP life cycle

The life cycle of State was studied above, while the life cycle of App defines the whole process of App from startup to exit. In the development, we often need to deal with some things in the corresponding APP life cycle, such as doing some accident operations in the appLication. OnCreate method in Android, and canceling network requests in the background when switching the foreground. How does that work in Flutter?

Use the WidgetsBindingObserver class to do lifecycle listening.

Pop Future<bool> didPopRoute() => Future<bool>.value(false); Push Future<bool> didPushRoute(String route) => Future<bool>.value(false); // System window related change callback, For example, rotate void didChangeMetrics() {} // Text scaling change void didChangeTextScaleFactor() {} // System brightness change void DidChangePlatformBrightness () {} / / localization language change void didChangeLocales (List < Locale > Locale) {} / / App void life cycle changes DidChangeAppLifecycleState (AppLifecycleState state) {} / / memory warning callback void didHaveMemoryPressure () {} / / callback the org.eclipse.swt.accessibility related characteristics void didChangeAccessibilityFeatures() {} }Copy the code

There are many interface callbacks in WidgetsBindingObserver, and we can listen for the corresponding callback method by setting a listener on a WidgetsBinding singleton. For details, please refer to the official documentation

Here we are concerned about most is the App lifecycle callback didChangeAppLifecycleState, and frame drawing callback addPostFrameCallback addPersistentFrameCallback.

DidChangeAppLifecycleState callback function, a parameter type for AppLifecycleState enumeration class, the enumeration class is a Flutter of App lifecycle state of encapsulation. Its common states are resumed, inactive, and paused.

  • Resumed: Is visible and can respond to user input.
  • Inactive: In inactive state, unable to process user responses.
  • Paused: Invisible and does not respond to user input, but continues the activity in the background.

A simple example: in the newly created Flutter_Demo, register the listener in initState, dispose remove the listener, switch from background to foreground and foreground to background respectively and see what changes are made.

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme .of(context) .textTheme .headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } @override void initState() { // TODO: implement initState super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { // TODO: implement dispose super.dispose(); WidgetsBinding.instance.removeObserver(this); } @override void didChangeAppLifecycleState(AppLifecycleState state) { // TODO: implement didChangeAppLifecycleState super.didChangeAppLifecycleState(state); print("didChangeAppLifecycleState::state=$state"); }}Copy the code
  • From the foreground to background, press the back button: AppLifecycleState. Inactive – > AppLifecycleState. Paused – > AppLifecycleState. Detached
  • From the foreground to background, press the home button: AppLifecycleState. Inactive – > AppLifecycleState. Paused
  • From the background to the front desk: AppLifecycleState. Resumed

Frame drawing callback

WidgetsBinding provides both single-pass Frame draw callback and real-time Frame draw callback mechanisms to meet different needs:

  • The single Frame draw callback is implemented via addPostFrameCallback. It calls back after the current Frame has been drawn, and only calls back once, and needs to be set again to listen again.

There is a requirement in development that when the page is visible and you do something else, you can listen for the first frame callback and then do something else.

WidgetsBinding. Instance. AddPostFrameCallback ((_) {print (" single Frame drawing callback "); // only one callback});Copy the code
  • Real-time drawing Frame callback, then through addPersistentFrameCallback implementation. This function is called back after each Frame drawing and can be used for FPS monitoring.
WidgetsBinding. Instance. AddPersistentFrameCallback ((_) {print (" real-time Frame drawing callback "); // every frame is called back});Copy the code

The scene used, a small example, is drawing the animation of rising bubbles. I can use Timer to draw, but with good performance of my phone, one frame ends. Wait a while before drawing the next frame; If the performance of the phone is poor, I didn’t finish the previous frame, you started the next frame, this is not to be stuck rhythm? This time can listening through addPersistentFrameCallback, after completion of this frame to draw the next frame, it’s much better than the timer.

Some study sites

  • Flutter. Dev/docs/cookbo…
  • book.flutterchina.club/

Refer to the link

  • Blog.csdn.net/baoolong/ar…