Android


If you’re an Android development engineer, you’re no stranger to the Activity lifecycle

  • onCreate
  • onStart
  • onResume
  • onPause
  • onStop
  • onDestroy

iOS


If you’re an iOS developer, you’re already familiar with the UIViewController lifecycle.

  • viewDidLoad
  • viewWillAppear
  • viewDidAppear
  • viewWillDisappear
  • viewDidDisappear
  • viewDidUnload

Flutter


Now that you know the lifecycle of Android and iOS, what about Flutter? Is there a mobile equivalent of the lifecycle function? If you know a little bit about Flutter, you’ll notice that there are two main widgets in Flutter: StatelessWidget and StatefulWidget. In this post, we’re going to focus on the StatefulWidget because it has a similar lifecycle to Android and iOS.

StatelessWidget


Stateless components are immutable, which means that their properties cannot change and all values are final. It can be interpreted as transforming incoming data into content for the interface to display, which will only be rendered once. For the stateless component lifecycle, there is only the build process. The build method of a stateless component is usually called only in three cases: when the widget is inserted into the tree for the first time, when the widget’s parent component changes its configuration, and when the InheritedWidget it depends on changes.

StatefulWidget


Stateful components hold state that may change during the life cycle of the widget and are defining interaction logic and business logic. It can be understood as an interface with dynamically interactable content that is rendered multiple times based on changes in data. Implementing a StatefulWidget requires at least two classes:

  •  One is the StatefulWidget class.

  • The other is the Sate class. the StatefulWidget class itself is immutable, but the State class is always present during the life cycle of the widget. the StatefulWidget stores its mutable state in a State object created by the createState method, or in an object to which the State is subscribed.

 StatefulWidget Life Cycle


  • createState: this function is the method to create state in StatefulWidget, when StatefulWidget is created, createState will be executed immediately. createState function is executed, it means the current component is already in the Widget tree, at this time, there is a very important attribute mounted is set to true.

  • initState: this function is called for State initialization, and will be called only once, so it is common to do some one-time operations in this callback, such as performing initial assignment of State variables, subscribing to event notifications of subtrees, interacting with the server side, and calling setState to set the State after getting the data from the server side.

  • didChangeDependencies: This function is called when the state of the component’s dependencies changes. The state we’re talking about here is the global state, such as the system language Locale or the application theme, etc. The Flutter framework will notify the widget to call this callback. Similar to the state stored by Redux on the frontend, this method is called when the state of the component changes to dirty and the build method is called immediately.

  • build: mainly returns the widget to be rendered. Since build will be called multiple times, only widget-related logic can be done in this function to avoid state exceptions caused by multiple executions.

  • reassemble: mainly used in development phase, in debug mode, this function will be called every time it is hot reloaded, so in debug phase, you can add some debug code in the meantime to check for code problems. This callback will never be called in release mode.

  • didUpdateWidget: this function is mainly called when the component is rebuilt, such as hot reloading, and the parent component is built, then the child component will call this method, and secondly, the method will definitely call the build method of this component again after the method is called.

  • deactivate: will be called after the component is removed from the node, and will continue to call dispose to remove it permanently if the component is removed from the node and then not inserted into another node.

  • dispose: permanently removes the component and releases its resources. After dispose is called, the mounted property is set to false, which also represents the end of the component’s lifecycle.

 A few concepts that are not life cycle but are very important


The following are not part of the life cycle, but play an important role in it.


  • mounted: is an important attribute in State, it is equivalent to an identifier, it is used to indicate whether the current component is in the tree or not. Before initState after createState, mounted will be set to true, which means the current component is already in the tree. When dispose is called, mounted is set to false, indicating that the current component is not in the tree.

  • dirty: means the current component is in dirty state, the build function will be executed in the next frame, and the state of the component will be dirty after calling the setState method or the didUpdateWidget method.

  • clean: opposite to dirty, clean means the current state of the component is clean, and the component will not execute the build function in clean state.

 The above figure shows the flutter lifecycle flowchart

 There are four broad phases


  1. Initialization phase, including two life cycle functions createState and initState;

  2. The component creation phase, including didChangeDependencies and build;

  3. Triggering multiple builds of a component, this stage may be triggered by didChangeDependencies, setState or didUpdateWidget, which will be triggered multiple times during the component’s runtime, which is also a point to focus on during the optimization process;

  4. Finally, there is the component destruction phase, deactivate and dispose.

 Component first load execution process


Let’s start by implementing the following code (similar to flutter’s own counter project) to see if the first creation of a Kangon component is executed in the order shown in the flowchart above.

  1.  Create a flutter project;

  2. Create count_widget.dart with the following code;
import 'package:flutter/material.dart';

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

  @override
  _CountWidgetState createState() {
    print('count createState');
    return _CountWidgetState();
  }
}

class _CountWidgetState extends State<CountWidget> {
  int _count = 0;
  void _incrementCounter() {
    setState(() {
      print('count setState');
      _count++;
    });
  }

  @override
  void initState() {
    print('count initState');
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print('count didChangeDependencies');
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(CountWidget oldWidget) {
    print('count didUpdateWidget');
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print('count deactivate');
    super.deactivate();
  }

  @override
  void dispose() {
    print('count dispose');
    super.dispose();
  }

  @override
  void reassemble() {
    print('count reassemble');
    super.reassemble();
  }

  @override
  Widget build(BuildContext context) {
    print('count build');
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            '$_count',
            style: Theme.of(context).textTheme.headline4,
          ),
          Padding(
            padding: EdgeInsets.only(top: 100),
            child: IconButton(
              icon: Icon(
                Icons.add,
                size: 30,
              ),
              onPressed: _incrementCounter,
            ),
          ),
        ],
      ),
    );
  }
}


The above code rewrites some of the life cycle of the StatefulWidget and prints the identifiers in the execution to make it easy to see the order of execution of the functions.


  1. Load the component in main.dart. The code is as follows:
import 'package:flutter/material.dart';

import './pages/count_widget.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() {
    return _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: CountWidget(),
    );
  }
}


At this point CountWidget acts as a subcomponent of MyHomePage. Let’s open the emulator and start running it. In the console you can see the following log, you can see that the first time the StatefulWidget is created it is calling the following four functions.

flutter: count createState
flutter: count initState
flutter: count didChangeDependencies
flutter: count build


By clicking the ➕ button on the screen, _count increases by 1 and the number on the simulator changes from 0 to 1. The log is as follows. This means that the functions setState and build are called when the state changes.

flutter: count setState
flutter: count build


After command + s hot reloading, the log is as follows:

flutter: count reassemble
flutter: count didUpdateWidget
flutter: count build


After commenting out the CountWidget in main.dart, command + s hot reloaded, this time the CountWidget disappeared from the emulator with the following log:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      // body: CountWidget(),
    );
  }
}
flutter: count reassemble
flutter: count deactivate
flutter: count dispose


After the above series of operations, through the log print and combined with the life cycle flow chart, we can clearly see the role of each life cycle function and understand the life cycle of several phases. I believe that many careful students have found a detail, that is, the build method is called in different operations, the following we will introduce what will trigger the component to build again.

 Trigger the component to build again


There are three ways to trigger a component to build again: setState , didChangeDependencies , didUpdateWidget .


1. setState is easy to understand, whenever the state of the component changes, it will trigger the component to build. During the above operation, clicking the ➕ button will increase the _count by 1, and the result is as shown in the following figure:


2. didChangeDependencies , build is also called when the global state on which the component depends changes. e.g. system language, etc., theme color, etc.


3. didUpdateWidget , we take the following code as an example. In main.dart, rewrite the lifecycle function in the same way and print it. Wrap a layer of Column around the CountWidget and create a RaisedButton of the same level as the counter in the parent widget.

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() {
    print('main createState');
    return _MyHomePageState();
  }
}

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

  void _changeMainCount() {
    setState(() {
      print('main setState');
      mainCount++;
    });
  }

  @override
  void initState() {
    print('main initState');
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print('main didChangeDependencies');
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(MyHomePage oldWidget) {
    print('main didUpdateWidget');
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print('main deactivate');
    super.deactivate();
  }

  @override
  void dispose() {
    print('main dispose');
    super.dispose();
  }

  @override
  void reassemble() {
    print('main reassemble');
    super.reassemble();
  }

  @override
  Widget build(BuildContext context) {
    print('main build');
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () => _changeMainCount(),
            child: Text('mainCount = $mainCount'),
          ),
          CountWidget(),
        ],
      ),
    );
  }
}

 Reload the app and you can see the print log as follows:

flutter: main createState
flutter: main initState
flutter: main didChangeDependencies
flutter: main build
flutter: count createState
flutter: count initState
flutter: count didChangeDependencies
flutter: count build

 It can be found:


  • The parent component also undergoes the four processes createState , initState , didChangeDependencies , build .

  • and the parent component will not create a child component until after build .


Click on the mainCount button of MyHomePage (the parent component) to print the following:

flutter: main setState
flutter: main build
flutter: count didUpdateWidget
flutter: count build


Click the ➕ button of the CountWidget to print the following:

flutter: count setState
flutter: count build


It can be shown that the parent component’s State change will cause the child component’s didUpdateWidget and build, and the child component’s own state change will not cause the parent component’s state to change.

 Component Destruction


We repeat the above and add a subcomponent CountSubWidget to the CountWidget and print the log with the count sub prefix. Reload the app.


Comment out the CountSubWidget in the CountWidget and print the log as follows:

flutter: main reassemble
flutter: count reassemble
flutter: count sub reassemble
flutter: main didUpdateWidget
flutter: main build
flutter: count didUpdateWidget
flutter: count build
flutter: count sub deactivate
flutter: count sub dispose


Reverting back to before the comment, comment out the CountWidget in MyHomePage and print the following:

flutter: main reassemble
flutter: count reassemble
flutter: count sub reassemble
flutter: main didUpdateWidget
flutter: main build
flutter: count deactivate
flutter: count sub deactivate
flutter: count sub dispose
flutter: count dispose


Since it is hot overloaded, it will call reassemble , didUpdateWidget , build and we can ignore the print log with these functions. We can conclude that the parent component is removed, the node is removed first, then the child component removes the node, the child component is permanently removed, and finally the parent component is permanently removed.

Flutter App Lifecycle


The lifecycle we described above is mainly the lifecycle of the StatefulWidget component. Below we will briefly describe the lifecycle related to the app platform, such as exiting to the background.


We create the app_lifecycle_state.dart file and create AppLifecycle, which is a StatefulWidget, but it inherits from WidgetsBindingObserver.

import 'package:flutter/material.dart';

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

  @override
  _AppLifecycleState createState() {
    print('sub createState');
    return _AppLifecycleState();
  }
}

class _AppLifecycleState extends State<AppLifecycle>
    with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    print('sub initState');
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    // TODO: implement didChangeAppLifecycleState
    super.didChangeAppLifecycleState(state);
    print('didChangeAppLifecycleState');
    if (state == AppLifecycleState.resumed) {
      print('resumed:');
    } else if (state == AppLifecycleState.inactive) {
      print('inactive');
    } else if (state == AppLifecycleState.paused) {
      print('paused');
    } else if (state == AppLifecycleState.detached) {
      print('detached');
    }
  }

  @override
  Widget build(BuildContext context) {
    print('sub build');
    return Container(
      child: Text('data'),
    );
  }
}

didChangeAppLifecycleState  ,AppLifecycleState :resumedinactivepauseddetached  。


The didChangeAppLifecycleState method relies on system notifications, which the app can normally receive, but in some cases it can’t, such as when the user shuts down the computer, etc. The four lifecycle states are described in detail in the source code. Its four lifecycle state enumerations are described in detail in the source code, which is attached below, along with a brief translation.


  • resumed: the application is visible and responds to user input. That is, the application comes to the foreground.

  • inactive: The application is in an inactive state and is not receiving input from the user. On iOS, this state corresponds to an application or Flutter host view running in an inactive state in the foreground. When in a phone call, responding to a TouchID request, entering the app switcher or control center, or when a UIViewController-hosted Flutter app is transitioning. On Android, this is equivalent to the app or Flutter host view running in an inactive state in the foreground. The app transitions to this state when another activity is being focused on, such as a split-screen app, phone call, picture-in-picture app, system dialog box, or other window. That is, the app goes into the background.

  • pause: the application is currently invisible to the user, unresponsive to user input, and running in the background. When the application is in this state, the engine will not be called. This means that the application enters an inactive state.

  • detached: the application is still hosted on the flutter engine, but detached from any host views. Timing for this state: when the engine is first loaded to attach to a platform View, or when the view is destroyed due to the execution of a Navigator pop.


In addition to the app lifecycle methods, Flutter has some other methods that are not part of the lifecycle, but are also observed at some special times, such as didChangeAccessibilityFeatures  , didHaveMemoryPressure 
, didChangeLocales   , didChangeTextScaleFactor  , etc. If interested, you can try them out.

By lzz

Leave a Reply

Your email address will not be published. Required fields are marked *