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 method
BuildContext
Where 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 use
StatelessWidget
orStatefulWidget
, its internalbuild()Or the latterState
–build()Method, all return oneWidget
Object; - Both components inherit from
Widget
, there is acreateElement()Abstract method, which returns one by defaultStatexxxElement(this)
, itsthis
On behalf of the currentwidgetInstance; - StatelessElement inherits from
ComponentElement
, itsbuild()The default is to call usWidgetThe build(context) method in the - Because ComponentElement inherits from
Element
Element implementsBuildContext
Interface, so we can get BuildContext in the Widget’s build(Context) method;