Based on the Widget

In Fluter, almost everything is a widget. Unlike native development, widgets are much broader in scope. They can represent not only UI elements, but also functional components, such as a widget for gesture detection, a Theme for Theme data delivery, and so on. So, for the most part, you can think of a widget as just a control, not a concept

The Widget’s function is to “describe the configuration data of a UI Element.” A Widget does not represent the display Element that will eventually be drawn on the screen. The one that is being drawn on the screen is Element

The Widget with the Element

The function of a Widget in Flutter is to “describe the configuration data of a UI element”. That is, the Widget does not represent the final display element to be drawn on the device screen, but rather describes the configuration data of the display element

In fact, the class that actually represents the Element displayed on the screen in Flutter is Element. That is, the Widget simply describes the Element’s configuration data. A Widget is just one configuration data for a UI Element, and a Widget can correspond to multiple elements. This is because the same Widget can be added to different parts of the UI tree, and when it is actually rendered, each Element in the UI tree corresponds to a Widget object. To sum up:

  • The Widget is essentially the configuration data for the Element. The Widget tree is actually a configuration number, and the actual rendering UI tree is made up of elements

    However, since elements are generated from widgets, there is a correspondence between them. In most scenarios, we can generally think of Widget trees as UI control trees or UI rendering trees

  • A Widget object can correspond to multiple elements. This is easy to understand, based on the same configuration (Widget), you can create multiple instances (Element)

The Widget class

abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });

  final Key? key;

  @protected
  @factory
  Element createElement();

  @override
  String toStringShort() {
    final String type = objectRuntimeType(this.'Widget');
    return key == null ? type : '$type-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  @override
  @nonVirtual
  bool operator= = (Object other) => super == other;

  @override
  @nonVirtual
  int get hashCode => super.hashCode;

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

  static int _debugConcreteSubtype(Widget widget) {
    return widget is StatefulWidget ? 1 :
           widget is StatelessWidget ? 2 :
           0; }}Copy the code
  • The Widget class inherits fromDiagnosticableTree.DiagnosticableTreeThe diagnostic tree provides debugging information
  • Key: This Key property is similar to the React/Vue Key property. It is used to decide whether to reuse the old widget in the next buildcanUpdate()Methods.
  • CreateElement () : As mentioned earlier, a Widget can correspond to multiple Elements. When the Flutter Framework builds the UI tree, this method is called to generate the Element object of the corresponding node. This method is implicitly invoked by the Flutter FrameWork and will not be invoked during our development.
  • _debugConcreteSubtype Overrides the parent class’s methods, which are mainly diagnostic tree features
  • CanUpdate () is a static method that can be used to reuse widgets during Widget tree rebuild builds.Whether to update the corresponding UI tree with the new Widget objectElementObject configuration; Through its source code we can see as long asnewWidet witholdWidgetruntimeTypekeyIt’s used when it’s equalnewWidgetTo updateElementObject, or a new one will be createdElement.

In addition, the Widget class itself is an abstract class, the core of which is the createElement() interface. In Flutter development, we usually do not inherit the Widget class directly to implement a new build. We often inherit Widget classes indirectly by inheriting StatelessWidget or StatefulWidget, both of which inherit from Widget classes, and these are very important abstract classes that introduce both models in widgets. I’ll focus on these two classes next

StatelessWidget

  • Stateless component

  • Inheriting from the Widget class, overriding the createElement() method

    @override
    StatelessElement createElement() => StatelessElement(this);
    Copy the code

    Create a StatelessElement object that indirectly inherits from the Element class and corresponds to the StatelessWidget (as its configuration data)

  • StatelessWidget is used in scenarios where no state needs to be maintained (that is, the UI cannot be modified), and it typically builds the UI by nesting other widgets in the build method, recursively building its nested widgets during the build process.

  • chestnuts

    class Echo extends StatelessWidget {
      final String text;
      final Color backgroundColor;
    
      const Echo({Key key, @required this.text, this.backgroundColor: Colors.green})
          : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Container(
            child: Text("hello word"), color: backgroundColor, ) ); }}Copy the code

    The code above implements an Echo Widget that returns a string

    The widget’s constructor parameters should use named parameters, with the @required annotation for required parameters to facilitate static code profilers. In addition, when inheriting a widget, the first parameter is usually key, and if the widget needs to receive from the widget, the Child or children parameter should usually be placed at the end of the parameter list. The properties of widgets should be declared final as much as possible to prevent accidental changes

    You can use it in the following ways

    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            title: "Widget relevant",
            theme: ThemeData(primaryColor: Colors.blue),
            home: Echo(text: "hello word")); }}Copy the code
  • Context

    Build takes a context argument, which is an instance of the BuildContext class and represents the context of the current widget in the widget tree. Each widget has a context object (because each widget is a node in the widget tree). In effect, the context is a handle to perform “related actions” on the current widget at its location in the Widget tree, such as walking up the widget tree from the current widget and finding the parent widget method

    class ContextRoute extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("The Context test"),
          ),
          body: Container(
            child: Builder(builder: (context) {
              // Look up the nearest parent 'Scaffold' Widget in the Widget tree
              Scaffold scaffold = context.findAncestorWidgetOfExactType<Scaffold>();
              // Return the AppBar title directly, which is actually Text("Context test ")
              return (scaffold.appBar asAppBar).title; }),),); }}Copy the code

StatefulWidget

  • Stateful components
  • Like the StatelessWidget, the StatefulWidget inherits from the Widget class and overwrites the createElement method, except that it returns a different Element object. In addition, a new interface createState() has been added to the StatefulWidget class.
  • Consists of at least two classes, one StatefulWidget and one State class
  • The StatefulWidget class itself is immutable, but the State held in the State class may change during the widget’s life cycle
abstract class StatefulWidget extends Widget {
    
  const StatefulWidget({ Key? key }) : super(key: key);

  @override
  StatefulElement createElement() => StatefulElement(this);

  @protected
  @factory
  State createState();
}
Copy the code
  • The StatefulElement indirectly inherits the Element class, and corresponding to the StatefulWidget (as configuration data), the createState Element may be called multiple times in a Teflon Element to create a State object

  • CreateState was used to create Stateful widget-related state, which could be invoked several times during the Stateful widget’s life cycle. For example, when a Stateful widget was inserted into multiple off-days of the widget tree at the same time, the Flutter Framework would call this method to generate an independent State instance for each location. Essentially, a StatefulElement corresponds to a State instance

    Widget tree can refer to the Widget structure tree, but because widgets correspond to elements (one can be many), they can also mean “UI tree “in some scenarios (the Sdk documentation for Flutter)

State

A StatefulWidget corresponds to a State class. State represents the State to be maintained by the corresponding StatefulWidget. The State information stored in State can be:

  • It can be read synchronously when a widget is built
  • The State can be changed during the Widget lifecycle. When the State is changed, you can manually call the setState() method to notify the Flutter framework of the State change. The Flutter framework receives the message, Its build method is called to rebuild the Widget tree to update the UI

Two commonly used properties in State

  • Widget: Represents the widget instance associated with the Flutter framework. This association is set dynamically by the Flutter framework. However, this association is permanent because the widget instance of a node in the UI tree may change during the lifecycle when the WIDGET instance is rebuilt. The State instance will only be created when the widget is inserted into the tree for the first time. If the widget is modified during a rebuild, the Flutter Framework will dynamically set the State to the latest widget instance
  • Context StatefulWidget corresponds to BuildContext, which acts the same as StatelessWidget’s BuildContext

State lifecycle

  • nitState

    Called when the Widget is inserted into the Widget in the tree for the first time. The Flutter framework calls the callback only once for each State object. Therefore, the Flutter callback usually performs some one-time operations, such as State initialization, subscribing to subtree time notifications, and so on

    Couldn’t be callback call BuildContext dependOnInheritedWidgetOfExactType, reason is that after the initialization is complete, in the Widget tree InheritFromoWidget can also change, So the right thing to do is call it in the Build method or didChangeDependencies

  • didChangeDependencies()

    Called when the State object dependency changes

    Such as: An InheritedWidget is included in the build, and the InheritedWidget is changed in subsequent builds, The didChangeDependencies callback for the child widgets of the InheritedWidget will be called.

    A typical scenario is when the Flutter framework calls the widget for callbacks when the system language Locale or application theme changes

  • build()

    It is used to build Widget subtrees and is called in the following scenario

    1, after initState is called

    2, after calling didUpdateWidget()

    3, after calling setState()

    4, after calling didChangeDependencies()

    5. Remove a position from the State object tree (deactivate is called) and reinsert it to another position in the tree

  • reassemble()

    This callback is provided specifically for development debugging and is called on hot reload, never in Release mode

  • didUpdateWidget()

    When a widget is rebuilt, the Flutter framework calls Widget.canUpdate to detect the old and new nodes at the same location in the widget tree and determine whether an update is needed. This callback is called if widget.canupdate returns true.

    As mentioned earlier, widget.canUpdate returns true if the key and runtimeType of the new and old widget are equal. That is, this method is called when the value of the new and old equal widgets can be equal to the runtimeType at the same time

  • deactivate()

    This callback is called when the State object is removed from the tree.

    In some scenarios, the Flutter framework inserts the State object back into the tree if the subtree containing the substate object moves from one position in the tree to another (this can be done by GlobalKey). If it is not re-inserted into the tree after removal, the Dispose () method is immediately called

  • dispose()

    Called when the State object is permanently removed from the tree; Resources are usually released in this callback

class CounterWidget extends StatefulWidget {
  final int counter;

  const CounterWidget({Key key, this.counter: 0}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return_CounterWidget(); }}class _CounterWidget extends State<CounterWidget> {
  int _counter;

  @override
  void initState() {
    super.initState();
    / / initialization
    _counter = widget.counter;
    print("InitState: initialization");
  }

  @override
  void didUpdateWidget(covariant CounterWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    print('didUpdateWidget: Widget Rebuild ');
  }

  @override
  void deactivate() {
    super.deactivate();
    print('Deactivate: State removed');
  }

  @override
  void reassemble() {
    super.reassemble();
    print('Reassemble: Hot reload');
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('didChangeDependencies: State Object dependencies change ');
  }

  @override
  void dispose() {
    super.dispose();
    print('Dispose: State Permanently removed');
  }

  @override
  Widget build(BuildContext context) {
    print('Build: Build widget');
    return Scaffold(
        body: Center(
            child: FlatButton(
      child: Text("$_counter"), onPressed: () => setState(() => ++_counter), ))); }}Copy the code

A small chestnut counter to observe the life cycle changes

1. First, open the page and view the output

I/flutter ( 6725): initState: initializes I/flutter (6725): didChangeDependencies: State the object dependencies change I/flutter (6725): Build: Build widgetsCopy the code

2. Click the hot reload button and call as follows

I/flutter ( 6725): reassemble: hot reload I/flutter (6725): didUpdateWidget: Widget re-builds I/flutter (6725): Build: Build widgetsCopy the code

3, click the number button, call as follows

I/flutter ( 6725): Build: Build widgetsCopy the code

4. Remove CountWidget from the Widget tree

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: "Widget relevant",
        theme: ThemeData(primaryColor: Colors.blue),
        // home: CounterWidget(counter: 0)
      	home:Text("hello word")); }}Copy the code

Then hit hot reload and call:

I/flutter ( 7366): deactivate: State removed I/flutter (7366): Dispose: State Permanently removeCopy the code

The lifecycle diagram is shown below:

Get the State object

Because the specific logic of the StatefulWidget is in its State, many times we need to get the State object corresponding to the StatefulWidget to call some methods. There are two ways to get the State object of the parent StatefulWidget in the child Widget tree

Get it by Context

The context object has a findAncestorStateOfType() method that looks up the State object corresponding to the StatefulWidget of the specified type up the Widget tree from the current node,

 // Find the ScaffoldState object corresponding to the ScaffoldState of the parent Scaffold
ScaffoldState _state = context.findAncestorStateOfType<ScaffoldState>();
Copy the code

Through the static method of

 ScaffoldState _state = Scaffold.of(context);
Copy the code

Through GlobalKey

1, add GlobalKey to the target StatefulWidget

2. Use GlobalKey to retrieve the State object

// Define a globalKey. Since globalKey is to be globally unique, we use static variable storage
staticGlobalKey<ScaffoldState> _globalKey= GlobalKey(); . Scaffold( key: _globalKey ,/ / set the key...).Copy the code

Note: Using GlobalKey is expensive and should be avoided if there is an alternative, and the same GlobalKey must be unique throughout the widget tree and cannot be repeated


This article refers to the Self-flutter Practice (book)