An overview of the

[BuildContext] objects are actually [Element] objects. The [BuildContext] ,interface is used to discourage direct manipulation of [Element] objects.

The translation means that the [BuildContext] object is actually the [Element] object. The [BuildContext] interface prevents direct manipulation of the [Element] object.

According to the official comments, we know that BuildContext is actually an Element object, mainly to prevent developers from manipulating Element objects directly. We can also see from the source that Element implements BuildContext, an abstract class

abstract class Element extends DiagnosticableTree implements BuildContext {}
Copy the code

The role of BuildContext

The relationship between Element and Widget was discussed in a previous article, but if it’s not clear, take a look

An Element is an instance of a specific location in the Widget tree, where the state of the Widget is stored.

So what exactly does BuildContext do? BuildContext can do almost anything Element can do, such as:

var size = (context.findRenderObjec  var size = (context.findRenderObject() as RenderBox).size;
var local = (context.findRenderObject() as RenderBox).localToGlobal;
varwidget = context.widget; t()as RenderBox).size;
Copy the code

For example, get the width before passing the context above, the offset from the upper left corner, the widget corresponding to element, etc

Since Elment inherits BuildContext, we can even use context to refresh Element’s state directly, as follows:

(context as Element).markNeedsBuild();
Copy the code

This makes it possible to refresh the current Element directly without going through SetState, but this is highly discouraged.

In SetState, the markNeedsBuild method is called as follows:

void setState(VoidCallback fn) { assert(fn ! = null); assert(() { if (_debugLifecycleState == _StateLifecycle.defunct) { ///...... } if (_debugLifecycleState == _StateLifecycle.created && ! mounted) { ///...... } return true; } ()); final dynamic result = fn() as dynamic; assert(() { if (result is Future) { ///...... ] ); } return true; } ()); /// Finally call _element! .markNeedsBuild(); }Copy the code

Another problem we’ll see when we’re writing code is that the state to update doesn’t have to be in setState, just above setState, and that’s fine. For example, some other reactive frameworks don’t have this callback, they just provide a way to tell the page to refresh. So did the early Flutter. However, the disadvantages of this problem were finally found. For example, most people would add a setState at the end of each method, resulting in excessive overhead. Moreover, when deleting the method, they did not know whether the setState had practical significance, which would cause some unnecessary troubles.

Therefore, Flutter adds a callback to setState. We can place the updated state directly inside the callback and place the state outside.


Some common methods

  • (context as Element).findAncestorStateOfType()

    Look up the current Element until after a particular type, and return its State

  • (context as Element).findRenderObject()

    Gets the object that Element renders

  • (context as Element).findAncestorRenderObjectOfType()

    Iterate up to get the render object corresponding to the generic

  • (context as Element).findAncestorWidgetOfExactType()

    Walk through to get the Widget corresponding to T

Some of the above methods are used in the source code, for example:

  • Scaffold.of(context).showSnackBar()

A SnackBar is displayed at the bottom of the Scaffold

static ScaffoldState of(BuildContext context) {
  assert(context ! =null);
  final ScaffoldState? result = context.findAncestorStateOfType<ScaffoldState>();
  if(result ! =null)
    return result;
/ /...
}
Copy the code

If you look at the OF method, you can see that the findAncestorStateOfType method is used to get the Scaffold state and perform some operations.

  • Theme.of(context).accentColor

    We can use the above method to get the theme color, etc., its internal implementation is as follows:

    static ThemeData of(BuildContext context) {
      final _InheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedTheme>();
      final MaterialLocalizations? localizations = Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
      finalScriptCategory category = localizations? .scriptCategory ?? ScriptCategory.englishLike;finalThemeData theme = inheritedTheme? .theme.data ?? _kFallbackTheme;return ThemeData.localize(theme, theme.typography.geometryThemeFor(category));
    }
    Copy the code

    It’s the same as above, finding the _InheritedTheme closest to you and finally giving it back to you


chestnuts

Write a slider and open it by clicking the button

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    returnScaffold( appBar: AppBar(title: Text(widget.title)), drawer: Drawer(), floatingActionButton: FloatingActionButton(onPressed: () { Scaffold.of(context).openDrawer(); })); }}Copy the code

Scaffold. Of () called with a context that does not contain a Scaffold.

I can’t open a Drawer because I didn’t find Drawer in the current context.

Why is that? Because this context is in the current MyHomePage hierarchy, there’s really no Drawer on top of it, so there’s no way to open it. So how to solve it? As follows:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    returnScaffold( appBar: AppBar(title: Text(widget.title)), drawer: Drawer(), floatingActionButton: Floating()); }}class Floating extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    returnFloatingActionButton(onPressed: () { Scaffold.of(context).openDrawer(); }); }}Copy the code

Modify to the above code can be solved.

The Floating context is the hierarchy below MyHomePage, so the Scaffold is scaffolds.

But in this case, we don’t need to create an extra component, so we need a better solution, like this:

class _MyHomePageState extends State<MyHomePage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), drawer: Drawer(), floatingActionButton: Builder( builder: (context) { return FloatingActionButton(onPressed: () { Scaffold.of(context).openDrawer(); }); })); }}Copy the code

We can use Builder to create an anonymous component.


reference

Uncle Wang of station B is not bald

If this article is helpful to your place, we are honored, if there are mistakes and questions in the article, welcome to put forward!