Study harmoniously! Don’t be impatient!! I’m your old friend, Xiao Qinglong

preface

Life cycle, is the system in different stages of the program (such as initialization, imminent, destruction, etc.) to return a method to us, simply put [life cycle] is [system callback method].

If you have experienced iOS development, you know that in iOS, the lifecycle method is:

  • Init (init initialization)

  • AwakeFromNib (Nib loaded successfully)

  • LoadView (loadView)

  • viewDidLoad

  • ViewWillAppear :(BOOL)animated (viewWillAppear before screen)

  • ViewWillLayoutSubviews (subviews to be adjusted)

  • viewDidLayoutSubviews

  • ViewDidAppear :(BOOL)animated (view rendered on screen)

  • ViewWillDisappear :(BOOL)animated (view will be removed from screen)

  • ViewDidDisappear :(BOOL)animated (the view has been removed from the screen)

  • Dealloc (View destroyed, released here)

  • didReceiveMemoryWarning

And there’s also a lifecycle method in Android.

There is a saying about Flutter: Everything is a Widget. What are the lifecycle methods for widgets?

1. Lifecycle approach

Widgets are classified into stateless widgets

  • StatefulWidget (stateful)

  • StatelessWidget (stateless)

1.1 StatelessWidget lifecycle method

The StatelessWidget is a “StatelessWidget,” meaning that it cannot be modified once created, so it has only two methods

  • A constructor
  • build

1.2 StatefulWidget lifecycle methods

StatefulWidget is a “StatefulWidget” that can refresh the Widget structure with setState, so it has more lifecycle methods:

  • A constructor

  • CreateState method

  • InitState method

  • DidChangeDependencies method (called when the hierarchy changes)

  • DidUpdateWidget method (called when data changes)

  • The build method

  • SetState method (requires active code invocation)

  • Deactivate method (called when a component is removed)

  • Dispose method (called when widgets are destroyed)

I wrote two test pages as follows (the code follows) :

When I click [red font] on the left, it will jump to the right page. Now the console prints:

At this point, I clicked the “blue button +” and the number on the page changed from 0 to 1. The console printed the following:

The setState method triggers another call to the build method.

Now, I click the “back” button in the upper left corner and the page returns successfully. Look at the console again:

So the StatefulWidget lifecycle methods are executed in the following order:

  1. A constructor

  2. CreateState method

  3. InitState method

  4. DidChangeDependencies method

  5. The build method (executed by default the first time, the setState method also triggers build)

  6. SetState method (requires active code invocation)

  7. Deactivate method (called when a component is removed)

  8. Dispose method (called when widgets are destroyed)

Missing is the analysis of the didUpdateWidget method, which varies with the setState of the parent Widget, simply put:

  • The parent widgetCall the [setState] method,The child widgetsThe didUpdateWidget method will be called.

2. Test code

main.dart

import 'package:dart_life_cycle/page_two.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: constMineAPP(), ); }}class MineAPP extends StatefulWidget {
  const MineAPP({Key? key}) : super(key: key);

  @override
  _MineAPPState createState() => _MineAPPState();
}

class _MineAPPState extends State<MineAPP> {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: GestureDetector(
      child: const Text('Click on me.'),
      onTap: () {
        Navigator.of(context).push(MaterialPageRoute(
            builder: (BuildContext context) => PageTwo(
                  title: ' '))); })); }}Copy the code

page_two.dart

import 'package:flutter/material.dart'; class PageTwo extends StatefulWidget { final String title; PageTwo({Key? Key, required this.title}) : super(key: key) {print(' constructor called '); } @override _PageTwoState createState() {print('createState method is called '); return _PageTwoState(); } } class _PageTwoState extends State<PageTwo> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) {print('build method is called '); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } @override initState() {print('initState method is called '); super.initState(); } @override void setState(VoidCallback fn) {print('\n\nsetState '); super.setState(fn); } @override void didChangeDependencies() {print('didChangeDependencies method is called '); super.didChangeDependencies(); } @override void didUpdateWidget(covariant PageTwo oldWidget) {print('didUpdateWidget method called '); super.didUpdateWidget(oldWidget); } @override void deactivate() {print('deactivate '); super.deactivate(); } @override void dispose() {print('dispose method is called '); super.dispose(); }}Copy the code

DidUpdateWidget method

When data changes, the didUpdateWidget method is called before the build method is called.Copy the code

The test code

import 'package:flutter/material.dart';

/// WidgetA
class WidgetA extends StatefulWidget {
  const WidgetA({Key? key}) : super(key: key);

  @override
  _WidgetAState createState() => _WidgetAState();
}

class _WidgetAState extends State<WidgetA> {
  late int count = 0;

  @override
  Widget build(BuildContext context) {
    print('Widgeta-build method called');
    return Scaffold(
      appBar: AppBar(
        title: const Text('test didChangeDependencies'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            IconButton(
                onPressed: () {
                  setState(() {
                    count++;
                  });
                },
                icon: const Icon(Icons.eleven_mp)),
            Text('A display$count'),
            // const WidgetB(),
            Container(
              alignment: Alignment.center,
              width: 300,
              height: 300,
              color: Colors.red,
              child: WidgetB(count: count),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void didUpdateWidget(covariant WidgetA oldWidget) {
    print('Widgeta-DidupDateWidget method called');
    super.didUpdateWidget(oldWidget); }}/// WidgetB
class WidgetB extends StatefulWidget {
  final int count;
  const WidgetB({Key? key, required this.count}) : super(key: key);

  @override
  _WidgetBState createState() => _WidgetBState();
}

class _WidgetBState extends State<WidgetB> {
  @override
  Widget build(BuildContext context) {
    print('Widgetb-build method called');
    return Container(
        width: 200,
        height: 200,
        color: Colors.yellow,
        child: Column(
          children: [
            Text('B shows${widget.count}'),
            WidgetC(count: widget.count),
          ],
        ));
  }

  @override
  void didUpdateWidget(covariant WidgetB oldWidget) {
    print('Widgetb-DidupDateWidget method called');
    super.didUpdateWidget(oldWidget); }}/// WidgetC
class WidgetC extends StatefulWidget {
  final int count;
  const WidgetC({Key? key, required this.count}) : super(key: key);

  @override
  _WidgetCState createState() => _WidgetCState();
}

class _WidgetCState extends State<WidgetC> {
  @override
  Widget build(BuildContext context) {
    print('Widgetc-build method called');
    return Container(
      child: Text('C shows${widget.count}'),
      alignment: Alignment.center,
      width: 100,
      height: 100,
      color: Colors.blueGrey,
    );
  }

  @override
  void didUpdateWidget(covariant WidgetC oldWidget) {
    print('Widgetc-didupDateWidget method called');
    super.didUpdateWidget(oldWidget); }}Copy the code

In this code, you change the count value by clicking the button on WidgetA. The count value is an argument to the child widget WidgetB and WidgetC constructors, so when WidgetA changes the count value and calls the setState method, WidgetB and WidgetC are refreshed.

Operation effect:

DidChangeDependencies method

When the widget hierarchy changes, the didChangeDependencies method is called before the Build method is called.Copy the code

The test code

import 'package:flutter/material.dart';

/// WidgetA
class WidgetA extends StatefulWidget {
  const WidgetA({Key? key}) : super(key: key);

  @override
  _WidgetAState createState() => _WidgetAState();
}

class _WidgetAState extends State<WidgetA> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    print('Widgeta-build method called');
    return Scaffold(
      appBar: AppBar(
        title: const Text('test didChangeDependencies'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            IconButton(
                onPressed: () {
                  setState(() {
                    count++;
                  });
                },
                icon: const Icon(Icons.eleven_mp)),
            Container(
              alignment: Alignment.center,
              width: 300,
              height: 300,
              color: Colors.red,
              child: count % 2= =0
                  ? (Container(
                      width: 50,
                      height: 50,
                      color: Colors.blue,
                    ))
                  : const WidgetB(),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void didChangeDependencies() {
    print('Widgeta-didChangeDependencies method called');
    super.didChangeDependencies(); }}/// WidgetB
class WidgetB extends StatefulWidget {
  const WidgetB({Key? key}) : super(key: key);

  @override
  _WidgetBState createState() => _WidgetBState();
}

class _WidgetBState extends State<WidgetB> {
  @override
  Widget build(BuildContext context) {
    print('Widgetb-build method called');
    return Container(
      width: 200,
      height: 200,
      color: Colors.yellow,
      alignment: Alignment.bottomCenter,
      child: const WidgetC(),
    );
// return;
  }

  @override
  void didChangeDependencies() {
    print('Widgetb-didChangeDependencies method called');
    super.didChangeDependencies(); }}/// WidgetC
class WidgetC extends StatefulWidget {
  const WidgetC({Key? key}) : super(key: key);

  @override
  _WidgetCState createState() => _WidgetCState();
}

class _WidgetCState extends State<WidgetC> {
  @override
  Widget build(BuildContext context) {
    print('Widgetc-build method called');
    return Container(
      alignment: Alignment.center,
      width: 100,
      height: 100,
      color: Colors.blueGrey,
    );
  }

  @override
  void didChangeDependencies() {
    print('WidgetC-didChangeDependencies method called');
    super.didChangeDependencies(); }}Copy the code

Operation effect:

Command + Click to enter:

Look at the comments

Called when a dependency of this [State] object changes.

For example, if the previous call to [build] referenced an
[InheritedWidget] that later changed, the framework would call this
method to notify this object about the change.

This method is also called immediately after [initState]. It is safe to
call [BuildContext.dependOnInheritedWidgetOfExactType] from this method.

Subclasses rarely override this method because the framework always
calls [build] after a dependency changes. Some subclasses do override
this method because they need to do some expensive work (e.g., network
fetches) when their dependencies change, and that work would be too
expensive to do for every build.

// Baidu TranslationCalled when the dependency of this [state] object changes. For example, if the last call to [build] that referenced [InheritedWidget] has since changed, the framework calls it to notify the object of the change. This method is also called immediately after [initState]. It is safe to do so This method call [BuildContext dependOnInheritedWidgetOfExactType]. Subclasses rarely override this method because the framework always calls [build] after a dependency changes. Some subclasses do cover it because they need to do some expensive work (for example, networking) and when their dependencies change, the work becomes very difficult and expensive per build.Copy the code

So we know that,

5. Interpretation of setState source code

The setState method in a StatefulWidget automatically triggers a callback to the exception build. When is its callback called?

To see where setState is called, click on it to access the source code:

5.1,First look at the English notes:

*/ 1, Notify the framework that the internal state of this object has changed. 2, The provided callback is immediately called synchronously. It must not return a future (the callback cannot be `async`), Since then it would be unbelievable when the state was actually being set. 3, Calling [setState] notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, Which causes the framework to schedule a [build] for this [State] object. 4, If you just change the State directly without calling [setState], the framework might not schedule a [build] and the user interface for this subtree might not be updated to reflect the 5, Generally it is recommended that the 'setState' method only be used to wrap the actual changes to the state, not any computation that might be associated with the change. For example, here a value used by the [build] function is incremented, and then the change is written to disk, but only the increment is wrapped in the `setState`: 6, It is an error to call this method after the framework calls. You can determine whether It is legal to call this method by checking whether the [mounted] property is true. 1. Notify the framework that the internal state of this object has changed. 2. The provided callback will be invoked synchronously immediately. It cannot return a future (the callback cannot be 'async'), because then it is not clear when the state was actually set. 3. A call to [setState] informs the framework that the way this object changes may affect the user interface subtree in the application, causing the framework to arrange [build] objects for this [state]. 4. If you change the state directly without calling [setState], the framework may not plan [build] for it and the user interface subtree may not update to reflect the new state. 5. In general, it is recommended to use the setState method only when wrapping the actual change to the state, rather than any calculation associated with the change that might occur. For example, [Build] uses a value function that increments and then writes the changes to disk, but only if the increments are wrapped in "setState". It is wrong to call this method after the framework calls dispose. You can check to see if it is legal to call this method. The [Mounted] property is true.Copy the code

5.2. Then check the source code:

@protected
  void setState(VoidCallback fn) {
    assert(fn ! =null);
    assert(() {
      if (_debugLifecycleState == _StateLifecycle.defunct) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() called after dispose(): $this'),
          ErrorDescription(
            'This error happens if you call setState() on a State object for a widget that '
            'no longer appears in the widget tree (e.g., whose parent widget no longer '
            'includes the widget in its build). This error can occur when code calls '
            'setState() from a timer or an animation callback.',
          ),
          ErrorHint(
            'The preferred solution is '
            'to cancel the timer or stop listening to the animation in the dispose() '
            'callback. Another solution is to check the "mounted" property of this '
            'object before calling setState() to ensure the object is still in the '
            'tree.',
          ),
          ErrorHint(
            'This error might indicate a memory leak if setState() is being called '
            'because another object is retaining a reference to this State object '
            'after it has been removed from the tree. To avoid memory leaks, '
            'consider breaking the reference to this object during dispose().',)]); }if(_debugLifecycleState == _StateLifecycle.created && ! mounted) {throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() called in constructor: $this'),
          ErrorHint(
            'This happens when you call setState() on a State object for a widget that '
            "hasn't been inserted into the widget tree yet. It is not necessary to call "
            'setState() in the constructor, since the state is already assumed to be dirty '
            'when it is initially created.',)]); }return true; } ());final Object? result = fn() as dynamic;
    assert(() {
      if (result is Future) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('setState() callback argument returned a Future.'),
          ErrorDescription(
            'The setState() method on $this was called with a closure or method that '
            'returned a Future. Maybe it is marked as "async".',
          ),
          ErrorHint(
            'Instead of performing asynchronous work inside a call to setState(), first '
            'execute the work (without updating the widget state), and then synchronously '
           'update the state inside a call to setState().',)]); }// We ignore other types of return values so that you can do things like:
      // setState(() => x = 3);
      return true; } ()); _element! .markNeedsBuild(); }Copy the code

The source code is a bit long, so let’s fold it up, something like this:

Let’s take a look at the assertions section of the code one by one (you can skip the assertions).

5.2.1 Assertion 1 Analysis

It’s a non-null judgment.

5.2.2 Assertion 2 Analysis

_debugLifecycleState explanation:

_StateLifecycle. Defunct explanation:

Mounted explanation:

You get the following simplified code

@protected
void setState(VoidCallback fn) {
  assert(fn ! =null);// Non-null judgment
  assert(() {
    // Determine whether dispose has already been called. Dispose cannot call setState again
    if (_debugLifecycleState == _StateLifecycle.defunct) {
        throwFlutterError.fromParts(...) ; }// Check whether the current object has been created and mounted is true.
    //
    Bool get mounted => _element! = null;" Mounted Specifies whether the mounted state is still in the tree.
    // Because the page can no longer be rendered when the page is released, calling setState returns an error
    if(_debugLifecycleState == _StateLifecycle.created && ! mounted) {throwFlutterError.fromParts(...) ; }return true; } ());final Object? result = fn() as dynamic;
  assert(...). ; _element! .markNeedsBuild(); }Copy the code
5.2.3 Assertion 3 Analysis
@protected
  void setState(VoidCallback fn) {
    assert(fn ! =null);// Non-null judgment
    assert(...).final Object? result = fn() as dynamic;
    assert(() {
      // Check whether the callback function fn is Future because the comment says "provided callback will be called immediately. It can't return a future."
      // If the callback is Future, an exception will be thrown
      if (result is Future) {
	throwFlutterError.fromParts(...) }} ()); _element! .markNeedsBuild(); }Copy the code
5.2.4, _element! MarkNeedsBuild () analysis

As we see, the last line of the setState source method is called

_element! .markNeedsBuild();Copy the code

Enter _element and you will see this code:

We found that:

  1. _elementIs of typeStatefulElement
  2. whilecontextThe return value of_element.
  3. setStateThe method is essentially called_element! .markNeedsBuild()

Therefore, we can guess:

  • SetState can be changedcontext! .markNeedsBuild()

Replace code, run, click button:

The setState method can use context! MarkNeedsBuild () instead. The setState method just has a little more assertion judgment.

5.2.5 how settate calls the Build method

This one, I can’t do it at this point

void scheduleFrame() => platformDispatcher.scheduleFrame();
Copy the code
/// Requests that, at the next appropriate opportunity, the [onBeginFrame] and
/// [onDrawFrame] callbacks be invoked.
///
/// See also:
///
///  * [SchedulerBinding], the Flutter framework class which manages the
///    scheduling of frames.
void scheduleFrame() native 'PlatformConfiguration_scheduleFrame';
Copy the code

So this content will be filled up later!!