The profile

This article is mainly about a problem I encountered in my actual study, which led to some thoughts. From this article you will learn as follows:

  • The reason behind Builder’s magic and simplicity
  • BuildContext’s true understanding
  • The relationship between widgets and Elements, and process analysis

background

As for the Builder widget, I think everyone found out about it by reporting an error.

From. Of (context), why the null pointer (Dart new feature), navigator.maybepop (context) exception, etc.

So here’s the simplest example:

code graphic

Detailed explanation is shown in the figure. Use the Form to verify that our input field is qualified.

As a new reader to Flutter, you will wonder why I am null, then a Google search will suggest you use Builder, and we will change the code to the following:

code legend

Oh yeah, come on, it’s that simple.

Wait, there’s something wrong, and as a conscientious, ethical, law-abiding, patriotic, hair-saving, new-age proletarian bread-maker, crazy stuff doesn’t seem to fit my mold, so I decided to dig into the details and see what you’re selling.


What is a Builder?

Official explanation:

A stateless utility widget whose [Build] method uses its [Builder] callbacks to create children of the widget.

The source code is as follows

class Builder extends StatelessWidget {

  const Builder({
    Key? key,
    required this.builder,
  }) : assert(builder ! =null),
       super(key: key);
       
  @override
  Widget build(BuildContext context) => builder(context);
}
Copy the code

When I looked at the source code for Builder, I never felt so smart and clever that a developer could read it. Very simple, on a hell of an interface callback, this is not readily can write a out.


The reason

So why doesn’t my own context work?

Let’s look at the form. of method first, but of course other of methods are similar.

static FormState? of(BuildContext context) {
  // Get the nearest widget of a given type T, which must be the type of a concrete [InheritedWidget] subclass, and register the build context with the widget so that it will be rebuilt when the widget changes (or when a new widget of that type is introduced, or the widget disappears), So that it can get the new value from the widget
  final _FormScope? scope = context.dependOnInheritedWidgetOfExactType<_FormScope>();
  returnscope? ._formState; }Copy the code

Ahem, simple understand dependOnInheritedWidgetOfExactType

Based on the context we passed in, this method looks up from its parent for a match to the currently given type and the most recent widget, and returns if it finds it, otherwise throws an exception.

Then let’s switch to the first screenshot and notice where I’ve circled it.

If you pass in a root context, you can’t expect from.of () to have FromState in the parent Widget tree.


Knowing why, you can even write your own widget instead of Builder, as in the following example.

The sample code animation

thinking

But does that end there? If, for this article, it is. But for myself, it raises even more questions:

  • What exactly does context do?
  • build(context)In the methodBuildContextWhere did it come from?

Widget and Element relationship

We often hear that Flutter has three trees, namely Widget, Element and RenderObject. We are mainly concerned with the first two.

Widget trees, as the name suggests, are our common components, which are nothing more than a configuration of our UI elements.

Element is the object that the Widget actually corresponds to. why? Did not understand, yes, actually I also did not understand 😂

The source code for Flutter is much simpler and easier to understand than the Android native.

Take the example of the commonly used StatelessWidget.

StatelessWidget

abstract class StatelessWidget extends Widget {...@override
  StatelessElement createElement() => StatelessElement(this);
  @protected
  Widget build(BuildContext context);
}
Copy the code

The common build(context) method is defined as an abstract method;

This implements the Widget’s abstract method createElement() and passes in our current instance object, so read on.


StatelessElement

class StatelessElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget as StatelessWidget;
	
  / / to highlight
  @override
  Widget build() => widget.build(this);
}
Copy the code

We mainly look at the build() method, which calls our statexxWidget-Build method, which implements the build of the widget and passes this, which is a StatelessElement, but all we get is BuildContext. So why is that? Let’s go ahead and look at ComponentElement.

Widget build(BuildContext context)
Copy the code

ComponentElement

// Used to build widgets
abstract class ComponentElement extends Element 
Copy the code

Element

  abstract class Element extends DiagnosticableTree implements BuildContext 
Copy the code

Element implements the BuildContext interface, so in StatelessElement-build(), you can pass Element directly into it.

Take a look at the official explanation for Element:

In short, Element represents the instance object of the Widget’s actual location in the tree. Why?

The Widget tree is a configuration tree, and the actual UI rendering tree is made up of elements. However, because elements are generated through widgets, there is a correspondence between them; Accordingly, a Widget object can correspond to multiple Element objects. It also makes sense that multiple instances (elements) can be created based on the same configuration (Widget).

BuildContext

We can understand that the BuildContext object is actually the Element object of the Widget. So we can access the Element object indirectly (through various XX.of) through the build method of the context in StatelessWidget and StatefulWidget, and the combination of widgets in our development, such as the collocation of widgets, They form our configuration tree, and the widget eventually corresponds to one element to another, forming an Emelent tree.


But why do we define BuildContext instead of an Element object?

The official explanation is as follows:

BuildContext is used to block direct operations on Element.

Obviously, this explanation is not easy to understand. After much thinking and scratching my head, my personal understanding is as follows:

In software development, any two things that are complexly coupled can be decouple through a third party, and BuildContext is just that.

Because our Element is responsible for the widget’s actual object, it has a lot of initialization and other methods that developers can’t call directly. If we expose it directly, the complexity will be significant, so it uses the BuildContext interface, While we can still manipulate Element indirectly to some extent, this third party approach is a good way to shield some features. For developers, we can only focus on widgets. For Element-related operations, The xx.of() method for the Widget allows us to focus on the Widget layer to a large extent, without having to worry about other aspects.


conclusion

1.context?

What is context?

The widgets we use are only configuration information. In fact, each widget has an Element. Since Element implements Buildcontext, in the statexxxWidget-build (context) method, Using context, we can indirectly manipulate Emelent to do something, Such as xx. Of (context) within it is called the dependOnInheritedWidgetOfExactType Element (that is, from the parent widget to start looking for matching), so we can think of:

The context is actually the actual location of our widget in the Element tree.

2.build(context)?

Where does BuildContext come from in the build(Context) method?

This problem is actually a brief summary of the source code:

  • We often useStatelessWidgetorStatefulWidget, its internalbuild()Or the latterStatebuild()Method, all return oneWidgetObject;
  • Both components inherit fromWidget, there is acreateElement()Abstract method, which returns one by defaultStatexxxElement(this), itsthisOn behalf of the currentwidgetInstance;
  • StatelessElement inherits fromComponentElement, itsbuild()The default is to call usWidgetThe build(context) method in the
  • Because ComponentElement inherits fromElementElement implementsBuildContextInterface, so we can get BuildContext in the Widget’s build(Context) method;