Fluent - life cycle

Keywords: iOS Flutter

1. Life cycle

The process of an object from creation to destruction is a life cycle, in other words, a callback method. It has been encapsulated in fluent to know what state the widget is in, and then give you a callback corresponding to the state. Therefore, the life cycle is actually a series of callback methods.

So what does the life cycle do?

  • Initialization data
    • Create variable, constant
    • Send network request
  • Listen for widget events
  • Manage memory
    • Destroy data, destroy listeners
    • Destroy timer, etc

2. Widget life cycle

There are two kinds of widgets: StatelessWidget and StatefulWidget. Here we first analyze the life cycle of StatelessWidget

2.1 common life cycle of statelesswidget

Create a new project, and then change the widget in home to Scaffold in MyApp.

class MyApp extends StatelessWidget {

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(),
        body: MyHomePage(title: 'Flutter Demo Page 2',),
      ),
    );
  }
}

Then change MyHomePage to StatelessWidget, where title is an optional parameter and can be empty, so it can be used when using! Force unpacking, or use?? To use the value on the right when the title is empty. If the title is not passed here, 123 will be displayed in Text.

class MyHomePage extends StatelessWidget {

  final String? title;

  MyHomePage({this.title});


  @override
  Widget build(BuildContext context) {
    return Center(child: Text(title ?? "123"),);
  }
}

Then add printing in the constructor and build function, and then look at the calling order and timing according to the printing.

class MyHomePage extends StatelessWidget {

  final String? title;

  MyHomePage({this.title}){
    print('The constructor was called');
  }


  @override
  Widget build(BuildContext context) {
    print('build The function was called');
    return Center(child: Text(title ?? "123"),);
  }
}

Here, there will be a small problem when android studio runs, that is, printing twice, and running with xcode will not have this problem. You can see here that the constructor is called before the build function.

2.1 common life cycle of statefulwidget

Change MyHomePage to StatefulWidget, and then add printing methods in Widget constructor, createState function, State constructor, initState function, dispose function and build function respectively.

class MyHomePage extends StatefulWidget {

  final String? title;
  MyHomePage({this.title}){
    print('Widget The constructor was called');
  }

  @override
  State<MyHomePage> createState() {
    print('createState The function was called');
    return _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage> {

  _MyHomePageState() {
    print('State The constructor was called');
  }
  @override
  void initState() {
    // TODO: implement initState
    print('initState The function was called');
    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    print('dispose The function was called');
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print('build The function was called');
    return Scaffold(
      appBar: AppBar(
      ),
      body: Center(child: Text(widget.title ?? '123'),)
    );
  }
}

After running, observe the printing sequence and find that it is Widget constructor - createState function - State constructor - initState function - build function - dispose function. The dispose function is not called here because it has not been destroyed.

So what methods are called during setState? Modify it_ MyHomePageState.

class _MyHomePageState extends State<MyHomePage> {
 int _count = 0;
  _MyHomePageState() {
    print('State The constructor was called');
  }
  @override
  void initState() {
    // TODO: implement initState
    print('initState The function was called');
    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    print('dispose The function was called');
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print('build The function was called');
    return Column(
      children: [
        ElevatedButton(onPressed: (){
         setState(() {
           _count ++;
         });
        }, child: const Icon(Icons.add)),
        Text('$_count'),
      ],
    );
  }
}

After hot overload, click the button and find that the hot overload calls the Widget constructor and build function. setState called the build function.

Click into the source code of setState to see that setState actually does only one thing_ element!.markNeedsBuild().

Here_ Element is of StatefulElement type, and you can see that context is_ element.

Can I call markNeedsBuild instead of setState? Experiment

 @override
  Widget build(BuildContext context) {
    print('build The function was called');
    return Column(
      children: [
        ElevatedButton(onPressed: (){
           _count ++;

         (context as StatefulElement).markNeedsBuild();
        }, child: const Icon(Icons.add)),
        Text('$_count'),
      ],
    );
  }

After running, it is found that it is OK, and the text on the screen has also changed. Of course, setState is recommended here because the system makes a lot of judgments in setState.

There is also a callback method of didChangeDependencies in the statefulWidget.

  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
  }

Will be called between the initState function and the build function. didChangeDependencies are callbacks that change dependencies.

Create an InheritedDemo component to test the didChangeDependencies. Here, set multiple levels test1, test2 and test3.

import 'package:flutter/material.dart';

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

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

class _InheritedDemoState extends State<InheritedDemo> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Test1(_count),
        ElevatedButton(onPressed: (){
          setState(() {
            _count++;
          });
        }, child: Text('I'm the button'))
      ],
    );
  }
}

class Test1 extends StatelessWidget {
  final int count;
  const Test1(this.count);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Test2(count);
  }
}

class Test2 extends StatelessWidget  {
  final int count;
  const Test2(this.count);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Test3(count);
  }
}
class Test3 extends StatefulWidget {
  final int count;
  const Test3(this.count);

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

class _Test3State extends State<Test3> {
  @override
  void didChangeDependencies() {
    // TODO: implement didChangeDependencies
    print('didChangeDependencies coming');
    super.didChangeDependencies();
  }
  @override
  Widget build(BuildContext context) {
      print('build coming');
    return Text(widget.count.toString());
  }
}

Go to MyApp and use InheritedDemo

 home: Scaffold(
        appBar: AppBar(),
        body: InheritedDemo(
        ),
      ),

You can share count at this time, but this method is too cumbersome. Then create a MyData class.

class MyData  extends InheritedWidget {
  final int data;//Data to be shared in subcomponents (save hits)
  // Construction method
  const MyData({required this.data,required Widget child}):super(child: child);

  // Define a convenient method to facilitate the Widget in the sub component to obtain shared data
  static MyData? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyData>();
  }

  // This callback determines whether to notify the child component of the Widget that depends on data when the current data changes
  @override
  bool updateShouldNotify(covariant MyData oldWidget) {
      // If true is returned, the didChangeDependencies of the Widget that depends on the data in the subassembly (there is usage data in the build function) will be called
      return oldWidget.data != data;
  }
}

In_ Use MyData in InheritedDemoState.

class _InheritedDemoState extends State<InheritedDemo> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return MyData(data: _count, child:  Column(
      children: [
        Test1(_count),
        ElevatedButton(onPressed: (){
          setState(() {
            _count++;
          });
        }, child: Text('I'm the button'))
      ],
    ));
  }
}

At this time, when you click the button, the didChangeDependencies function will be called and before the build function. If updateShouldNotify returns false, the didChangeDependencies function will not be called. The didChangeDependencies function is mainly used to save this data in didChangeDependencies if there are many logic dependencies on this data.

Only the sub components in MyData can share data. If you want the whole project to share, package a MyData in home.

3. Summary

  • Basic concepts of life cycle
    • What is the life cycle
      • To put it bluntly, it is a callback method (function)
      • Let you know what kind of state the Widget I encapsulated is in!
    • Does it work
      • Listen for Widget events
      • Initialization data
        • Create data
        • Send network request
      • Memory management
        • Destroy data, destroy listeners
        • Destroy Timer, etc
  • Life cycle of Widget
    • Stateless
      • 1. Construction method
      • 2. build method
    • Stateful (contains two objects Widget, State)
      • Widget construction method
      • CreateState of Widget
      • Construction method of State
      • initState method of State
      • didChangeDependencies method (change dependencies)
        • After the dependent InheritedWidget changes, the method will also be called!
      • build of State
        • When the setState method is called. build will be called again for rendering!
      • When the Widget is destroyed, State dispose is called

Posted by coffejor on Tue, 23 Nov 2021 04:15:28 -0800