As a mobile developer, one of the first questions about Flutter is: What is the life cycle of Flutter? How is the life cycle handled? Where is my onCreate()[Android]? ViewDidLoad () (iOS)? Where should MY business logic be handled? What about initializing data? Hope to read this article, you can have a little help.
The android
If you’re an Android developer, you’re no stranger to the Activity lifecycle
- onCreate
- onStart
- onResume
- onPause
- onStop
- onDestroy
iOS
If you’re an iOS developer, you already know the lifecycle of UIViewController.
- viewDidLoad
- viewWillAppear
- viewDidAppear
- viewWillDisappear
- viewDidDisappear
- viewDidUnload
Flutter
Knowing the Android and iOS life cycles, what about Flutter? Is there a life cycle function for mobile? If you know a little bit about Flutter before, you will have noticed that there are two main widgets in Flutter: stateless and statefulWidgets. In this article, we’ll focus on StatefulWidget because it has a similar life cycle to Android and iOS.
StatelessWidget
Stateless components are immutable, meaning that their properties cannot change and all values are final. Can be understood as external incoming data into the interface to display the content, rendering only once. The stateless component lifecycle consists of only the build process. The stateless component build method is typically invoked only when the widget is inserted into the tree for the first time, when the widget’s parent changes its configuration, and when the InheritedWidget it depends on changes.
StatefulWidget
Stateful components hold states that can change during the Widget life cycle, defining interaction logic and business logic. Think of it as a dynamic, interactive content interface that renders multiple times as the data changes. 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 throughout the Widget life cycle. The StatefulWidget stores its mutable State in a State object created by the createState method, or in an object subscribed to by that State.
StatefulWidget life cycle
- CreateState: this function is the method to createState in the StatefulWidget. CreateState is executed immediately when the StatefulWidget is created. The createState function completes, indicating that the component is already in the Widget tree. Mounted is set to true.
- InitState: This function is called only once for State initialization. Therefore, it usually does some one-time operations in this callback, such as performing the initial assignment of State variables, subscribing to the event notification of the subtree, interacting with the server, and calling setState to setState after obtaining the server data.
- DidChangeDependencies: This function is called when the State on which the component depends changes. The State referred to here is the global State, such as the system language Locale or application theme, and the Flutter framework notifies the widget to invoke this callback. Similar to the State stored by the front-end Redux. After this method is called, the component’s state changes to dirty and the build method is called immediately.
- Build: returns the widgets that need to be rendered. Since the build will be called multiple times, only the Widget logic can be used in this function to avoid status abnormalities caused by executing the build multiple times.
- Reassemble: Mainly used during development. In Debug mode, this function is called every time a hot reload occurs, so you can add debug code during this time to check for code problems. This callback is never called in Release mode.
- DidUpdateWidget: This function is called only when a component is rebuilt, such as a hot overload, and the parent component builds, and then the build method in the component is called.
- Deactivate: Called after a component has been removed from a node. If it has been removed and has not been inserted into another node, dispose is called to remove it permanently.
- Dispose: Permanently removes a component and releases component resources. After dispose is called, mounted is set to false, indicating the end of component life cycle.
Not the life cycle, but very important concepts
The following are not part of the lifecycle, but play an important role in it.
- Mounted: specifies whether the component is in the tree. Before createState and initState, Mounted is set to True, indicating that the component is in the tree. When dispose is called, Mounted is set to false, indicating that the component is not in the tree.
- Dirty: Indicates that the current component is dirty. The build function will be executed at the next frame. After the setState method is called or the didUpdateWidget method is executed, the component is dirty.
- Clean: Corresponding to dirty, clean indicates that the component is in the clean state. In the clean state, the component does not execute the build function.
The diagram above shows the flutter life cycle flow chart
There are roughly four stages
- Initialization phase, including two life cycle functions createState and initState;
- Component creation phase, including didChangeDependencies and Build;
- Trigger multiple builds of the component, which may occur as a result of didChangeDependencies, setState, or didUpdateWidget. This is also something to focus on during optimization;
- Finally there is the component destruction stage, deactivate and Dispose.
The component first loads the execution process
First let’s implement the following code (similar to flutter’s own counter project) to see if the first creation of the Kangkang component is performed in the order described in the flow chart.
- Create a FLUTTER project;
- Add the following code to create count_widget.dart;
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, ), ), ], ), ); }}Copy the code
The code above overwrites some of the life cycles of statefulWidgets and prints flags during execution to make it easy to see the order in which functions are executed.
- 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(), ); }}Copy the code
This time CountWidget is a child of MyHomePage. Let’s open up the simulator and run it. As you can see in the following log on the console, the StatefulWidget calls the following four functions when it is first created.
flutter: count createState
flutter: count initState
flutter: count didChangeDependencies
flutter: count build
Copy the code
Click the ➕ button on the screen, _count increases by 1, the number on the simulator changes from 0 to 1, and the log is as follows. The setState and build functions are called when the state changes.
flutter: count setState
flutter: count build
Copy the code
After command + S is hot reloaded, the log is as follows:
flutter: count reassemble
flutter: count didUpdateWidget
flutter: count build
Copy the code
Dart: CountWidget: CountWidget: CountWidget: CountWidget: CountWidget: CountWidget: CountWidget: CountWidget
class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), // body: CountWidget(), ); }}Copy the code
flutter: count reassemble
flutter: count deactivate
flutter: count dispose
Copy the code
After a series of operations above, we can clearly see the role of each lifecycle function and understand the phases of the lifecycle through log printing combined with the lifecycle flow chart. As many of you may have noticed, the build method is called in different operations. Here’s what happens when a component builds again.
Trigger the component to build again
There are three ways to trigger a component to build again: setState, didChangeDependencies, and didUpdateWidget.
SetState is easy to understand. It triggers a component build whenever the state of the component changes. During the above operation, click the ➕ button and _count is incremented by 1, resulting in the following image:
DidChangeDependencies call Build when the global state of a component’s dependencies changes. Such as system language, theme color, etc.
3. DidUpdateWidget, take the following code as an example. Dart overwrites the same lifecycle function and prints it. Outsource a layer of Column in CountWidget and create a sibling RaisedButton as a 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(), ], ), ); }}Copy the code
Reload the app and you can see the printed 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
Copy the code
It can be found:
- The parent component also experiences
createState
,initState
,didChangeDependencies
,build
These four processes. - And the parent component has to be in
build
Child components are created after that.
Click the mainCount button of MyHomePage and print the following:
flutter: main setState
flutter: main build
flutter: count didUpdateWidget
flutter: count build
Copy the code
Click on CountWidget’s ➕ button and print the following:
flutter: count setState
flutter: count build
Copy the code
State changes of the parent component cause didupDateWidgets and builds of the child component, and State changes of the child component do not cause State changes of the parent component.
Component destroyed
We repeat the previous steps by adding a child component to CountWidget, CountSubWidget, and printing the log with the count sub prefix. Reload the app.
Comment out the CountSubWidget in CountWidget and print the following log:
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
Copy the code
Before returning to 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
Copy the code
Reassemble, didUpdateWidget, build are called because they are hot overloads, and we can ignore the printed logs with these functions. It can be concluded that when the parent component is removed, the node is removed first, then the child component is removed permanently, and finally the parent component is permanently removed.
Flutter App Lifecycle
The life cycles described above focus on the life cycles of StatefulWidget components. Let’s take a brief look at app platform-specific life cycles, such as exiting to the background.
Dart file and create AppLifecycle, which is a StatefulWidget but inherits 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'), ); }}Copy the code
DidChangeAppLifecycleState method is the key, AppLifecycleState the status include: resumed, inactive, paused, detached.
DidChangeAppLifecycleState method depends on the system notification (notifications), under normal circumstances, the App can be to receive these notifications, but in a few cases cannot be received notice, such as user shutdown, etc. Its four life cycle state enumerations are described and explained in detail in the source code, the source code and simple translation instructions are attached below.
- Resumed: The application is visible and responds to user input. That is, the application comes to the foreground.
- Inactive: The application is inactive and does not receive input from the user. On iOS, this state corresponds to the foreground inactive state of the application or the Flutter host view. While on a phone call, responding to a TouchID request, entering an application switcher or control center, or while a UIViewController hosted Flutter application is transitioning. On Android, this is equivalent to the application or the Flutter host view running in a foreground inactive state. Applications transition to this state when another activity is focused, such as a split-screen app, a phone call, a picture-in-picture app, a system dialog box, or another window. So the application goes into the background.
- Pause: The application is currently invisible to the user, does not respond to user input, and runs in the background. When the application is in this state, the engine will not be called. That is, the application goes into an inactive state.
- Detached: The application is still hosted on the Flutter engine, but separated from any host view. When in this state: The engine is first loaded in the process of attaching to a platform View, or the View is destroyed due to executing a Navigator POP.
In addition to the app lifecycle method, Flutter has other methods that are not part of the lifecycle but can be observed at particular times. Such as didChangeAccessibilityFeatures (the current system changed some access to sexual activity callback), didHaveMemoryPressure (low memory callback), didChangeLocales (, when the user changes local Settings System language changes), didChange extScalefactor (text coefficient changes), etc., if you’re interested, try it out.
conclusion
This article focuses on the life cycle of StatefulWidget in widgets and the life cycle related to Flutter App. But remember, while StatefulWidget is good, don’t use it for all mindless widgets. Use statelessWidgets when you can (come to think of it, why?). . Well, by the end of this article, you are a junior Flutter development engineer and ready for your job interview.
The last
Really stick to the end of the people, often rely on not short passion, but just the right love and investment. You are so young that you can be anything you want to be!