The life cycle of states in a Flutter

The State class in Flutter is used in conjunction with the StatefulWidget to update the Widget. State is responsible for maintaining the State of statefulWidgets and saving their State information. When the state of the Widget changes, the user simply calls the setState() method and the Fultter engine rebuilds the Widget tree to update the UI. The setState() method is more like a message mechanism that notifies the Flutter engine that the status of the Widget has changed. The Flutter engine updates the Widget tree upon receiving the notification.

Because State manages the State information of the StatefulWidget and is responsible for updating the StateWidget controls, understanding its lifecycle is important for Flutter development. In some application scenarios we need to know when State and Widget are built, destroyed, and updated.

1. The introduction of the State

The interfaces of the State class are shown in the following code. The most common ones in our development are the Build () and setState() methods, which are used to build widgets and notify the Flutter engine to update the Widget tree, respectively. The State class holds a Widget object that reconstructs the Widget tree and holds a context for a Widget to retrieve its position in the Widget tree.

//State is a template virtual class that must be inherited to be used
@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
  T? _widget;  // An instance of State holds a Widget object
  StatefulElement? _element;  // Hold the StatefulElement object and build the Element tree
  BuildContext get context {  // Holds the context object, which is actually StatefulElement
    return_element! ; } Widget build(BuildContext context);// Build Widget tree
    
    void initState(){}  // Initialize State
    
    void didUpdateWidget(covariant T oldWidget) { }  / / update the Widget
    
    void reassemble() {}  // Reassemble the Widget, usually called when hot reloading occurs
    
    void setState(VoidCallback fn) {}  // Update State, more on that later
    
    void deactivate() {}  // Unlog the Widget to remove it from the tree without freeing resources.
    
    void dispose() {}  // Destroy the Widget, actually removing it from the tree, and freeing resources
      
    void didChangeDependencies() {}  // Change the dependency, the State dependency changes
}
Copy the code

In development, we simply override the build() method to define the child widgets and use the setState() method to inform the Flutter engine to update the UI. Where build() method is completely user-defined, here we give the main code of setState() to explore the operation mechanism of setState() method, the code is as follows:

  // The main code for the setState() method, omitting debugging information
  @protected
  void setState(VoidCallback fn) {  // Pass a callback function fn
    final dynamic result = fn() as dynamic; _element! .markNeedsBuild();// Mark Element as needing to be rebuilt ()
  }
  
  // The owner is BuildOwner and the debugging information is also omitted.
  voidmarkNeedsBuild() { owner! .scheduleBuildFor(this);  // This Element is passed into the Flutter engine's scheduling sequence, waiting for reconstruction
  }
Copy the code

According to the source code in setState(), the incoming callback will be run without affecting the build() process of the Flutter engine. In other words, even if the fn() callback is null, the Flutter engine will update the UI based on the existing values. At the code level the following two ways of writing are equivalent.

// Write method 1: Update the UI value, then call setState()
TextButton(
  child: Text('$_counter'),
  onPressed:(){
    ++_counter;  // Update the UI values that need to be changed
    setState(() {});  // Call setState() again});// Write mode 2: Update the UI value in the setState() body
TextButton(
  child: Text('$_counter'),
  onPressed:(){
    setState(() {
        ++_counter;  // Update UI values in the setState() method}); });Copy the code

But for the sake of readability, that is, to let the programmer know where we have refreshed the UI, we still place the value of the UI that needs to be updated in the callback body of setState().

2. Life cycle of State()

Knowing the life cycle of State() makes sense when using Flutter development. For example, if we need to initialize State with parameters in the Widget in some cases, we can initialize State with value=widget.initValue in initState(). Using the State constructor for initialization can lead to code redundancy and poor readability. In addition, knowing when State is built and when it is destroyed is important for program development.

In the previous section, we looked at the members of the State class in more detail, and here is a table that details the functions of methods in State.

methods role
initState() Called when State is initialized, inserted into the render tree, and only called once
didChangeDependencies() State is called when the dependent object changes, such as when the language or topic changes.
didUpdateWidget() Called when the component state changes, possibly multiple times
build() Build a Widget
deactivate() Logout removes State from the render tree, but does not destroy it yet
dispose() Destroy to remove State from memory
reassemble() Regroup, called when hot surfaces are hot loaded

Below we will explore the Flutter life cycle with a simple page jump code. The code consists of two pages, StateDemoPage1(page 1) and StateDemoPage2(page 2). They both have only one TextButton control. StateDemoPage1 implements a simple jump to StateDemoPage2. StateDemoPage2 implements a single click to change the color of a Button. Using this simple jump, we can observe the life cycle of State() as follows:

  // Page 1 code
  class StateDemoPage1 extends StatefulWidget{

  const StateDemoPage1({
    Key key
  });

  @override
  State<StatefulWidget> createState() {
    return new_StateDemoPage1(); }}class _StateDemoPage1 extends State<StateDemoPage1>{
  @override
  Widget build(BuildContext context) {
    print("_StateDemoPage1: Build (Create Widget, build Widget tree)");
    return Scaffold(
      body: Center(
        child: TextButton(
          child: Text('Skip to next page'),
          // The counter increases after clicking
          onPressed:(){
            Navigator.push(context, MaterialPageRoute(builder: (context){
              StateDemoPage2();
            }));
          },
          style: ButtonStyle(
            backgroundColor: MaterialStateProperty.resolveWith((states) => Colors.blue)
          )
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    print("_StateDemoPage1: initState(initialization State)");
  }

  @override
  void didUpdateWidget(covariant StateDemoPage1 oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("_StateDemoPage1: didUpdateWidget(UpdateWidget));
  }

  @override
  void deactivate() {
    super.deactivate();
    print("_StateDemoPage1: deactive(Unregister Widget, remove Widget from tree without releasing resources.) ");
  }

  @override
  void dispose() {
    super.dispose();
    print("_StateDemoPage1: Dispose (destroy Widget, actually remove Widget from tree, and free resources)");
  }

  @override
  void reassemble() {
    super.reassemble();
    print("_StateDemoPage1: Reassemble (Reassemble Widget)");
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("_StateDemoPage1: didChangeDependencies");
  }

  _StateDemoPage1(){
    print("_StateDemoPage1: Constructor (Changing dependencies, State dependencies changing)"); }}// Page 2 code
  class StateDemoPage2 extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    return new_StateDemoPage2(); }}class _StateDemoPage2 extends State<StateDemoPage2>{
  Color mBackGroundColor = Colors.redAccent;
  @override
  Widget build(BuildContext context) {
    print("_StateDemoPage2: Build (Create Widget, build Widget tree)");
    return Scaffold(
      body: Center(
        child: TextButton(
            child: Text('Here's the second page',
            style: TextStyle(
              color: Colors.white
            ),),
            onPressed: (){
              setState(() {
                mBackGroundColor=Colors.green;
              });
            },
            style: ButtonStyle(
                backgroundColor: MaterialStateProperty.resolveWith((states) => mBackGroundColor)
            )
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    print("_StateDemoPage2: initState(initialization State)");
  }

  @override
  void didUpdateWidget(covariant StateDemoPage2 oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("_StateDemoPage2: didUpdateWidget(UpdateWidget));
  }

  @override
  void deactivate() {
    super.deactivate();
    print("_StateDemoPage2: deactive(Unregister Widget, remove Widget from tree without releasing resources.) ");
  }

  @override
  void dispose() {
    super.dispose();
    print("_StateDemoPage2: Dispose (destroy Widget, actually remove Widget from tree, and free resources)");
  }

  @override
  void reassemble() {
    super.reassemble();
    print(_StateDemoPage2: Reassemble (Reassemble Widget));
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("_StateDemoPage2: didChangeDependencies");
  }

  _StateDemoPage2(){
    print("_StateDemoPage2: constructor"); }}Copy the code

Step1: click to start running

Initialization of State in page 1: when running page 1, the constructor for _StateDemoPage1 is run ->initState()->didChangeDependencies()->build().

Statedemopage1: constructor() _StateDemoPage1: constructor() _StateDemoPage1: initState DidChangeDependencies _StateDemoPage1: BuildCopy the code

Reassemble ()->didUpdateWidget()->build() when we heat reload page 1, _StateDemoPage1 runs: Reassemble ()->didUpdateWidget()->build().

Statedemopage1: constructor() _StateDemoPage1: constructor() _StateDemoPage1: initState Statedemopage1: stateDemopage1: Build Reassemble (reassemble Widget) _StateDemoPage1: didUpdateWidget(update Widget) _StateDemoPage1: Build (create Widget, build Widget tree)Copy the code

Step2: click the jump button in page 1 to jump to page 2

Initialization process of Page 2: At this point, page 2 enters the initialization process, and the build process is the same as page 1. Page 1 is invisible, but is not in the destruction process. The terminal output is as follows:

Statedemopage2: constructor _StateDemoPage2: initState _StateDemoPage2: Constructor Statedemopage2: StateDemopage2: BuildCopy the code

Step3: Click page 2 to change to the green button

Update process for page 2: At this point, the build() method is called to update the UI.

_StateDemoPage2: Build (Create Widget, build Widget tree)Copy the code

Step4: click on TopBar to return

Destruction process of page 2: At this point, we entered the destruction process and called the deactive()-> Dispose () method of _StateDemoPage2 to complete the destruction of page 2.

_StateDemoPage2: deactive(Unregister the Widget and remove it from the tree without releasing resources.) _StateDemoPage2: Dispose (dispose the Widget, actually remove it from the tree, and free the resource)Copy the code

The life cycle of a StatefulWidget can be roughly divided into three phases as shown in the figure below:

  • Constructor ->initState()->didChangeDependencies()->build()
  • Reassemble ()->didUpdateWidget()
  • Dispose process: Deactive ()-> Dispose ()

Welcome to attention

reference

[1] book. Flutterchina. Club/chapter3 / [2] blog.csdn.net/u011272795/…