Understand BuildContext in the article "fluent"

summary

[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 a [Element] object. The [BuildContext] interface is used to prevent direct manipulation of [Element] objects.

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

abstract class Element extends DiagnosticableTree implements BuildContext {}

Role of BuildContext

Before An article The corresponding relationship between Element and Widget has been described in. You can take a look at those that are not very clear

Element is the instance corresponding to a specific position in the Widget tree, and the state of the Widget will be saved in element.

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

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

For example, the width and height, the offset from the upper left corner, and the widget corresponding to element are obtained through the context above

Because Element inherits from BuildContext, we can even refresh the state of Element directly through context, as follows:

(context as Element).markNeedsBuild();

In this way, you can refresh the current Element directly without going through SetState, but this is extremely not recommended.

In fact, in SetState, the markNeedsBuild method is finally 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;
  }());
  ///Final call
  _element!.markNeedsBuild();
}

In the process of writing code, we will also find a problem, that is, the state to be updated does not have to be written in setState, but only on setState. This is no problem. For example, some other responsive frameworks do not have this callback, but only provide a method to notify page refresh, and the same is true for earlier flutter s. However, the disadvantages of this problem are finally found. For example, most people will add a setState after each method, resulting in excessive overhead, and they don't know whether the setState has practical significance when deleting it, which will cause some unnecessary trouble.

Therefore, Flutter adds a callback in setState. We can put the updated state directly in the callback, and those that have nothing to do with the state can be placed outside.

Some common methods

  • (context as Element).findAncestorStateOfType()

    Look up along the current Element until a specific type returns its State

  • (context as Element).findRenderObject()

    Gets the object rendered by Element

  • (context as Element).findAncestorRenderObjectOfType()

    Traverse up to get the render object corresponding to the generic

  • (context as Element).findAncestorWidgetOfExactType()

    Traverse to get the Widget corresponding to T

There are some chestnuts used in the above methods in the source code, such as:

  • Scaffold.of(context).showSnackBar()

A SnackBar is displayed at the bottom of Scaffold

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

Looking at the of method, you can find that the findAncestorStateOfType method is used to obtain the Scaffold state and finally implement some operations,

  • Theme.of(context).accentColor

    We can obtain the following theme colors through the above method, and 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);
      final ScriptCategory category = localizations?.scriptCategory ?? ScriptCategory.englishLike;
      final ThemeData theme = inheritedTheme?.theme.data ?? _kFallbackTheme;
      return ThemeData.localize(theme, theme.typography.geometryThemeFor(category));
    }
    

    It's the same as the one above. It's also the one closest to you_ Inherited theme, I'll give it back to you at last

Chestnuts

Write a sidebar and open the sidebar 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) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      drawer: Drawer(),
      floatingActionButton: FloatingActionButton(onPressed: () {
        Scaffold.of(context).openDrawer();
      }),
    );
  }
}

Run the code and you will find the error: scaffold of() called with a context that does not contain a Scaffold.

This means that no Drawer can be found in the current context, so it cannot be opened.

Why? Because this context is at the current MyHomePage level, there is no Drawer on its upper layer, so naturally there is no way to open it. So how to solve it? As follows:

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text(widget.title)),
        drawer: Drawer(),
        floatingActionButton: Floating());
  }
}

class Floating extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(onPressed: () {
      Scaffold.of(context).openDrawer();
    });
  }
}

It can be solved by modifying to the above code.

The context in Floating is the level below MyHomePage, so if its superior Scaffold, it will not report an error.

However, in general, we do not need to create one more component, so we need a better solution, as follows:

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();
            });
          },
        ));
  }
}

We can create an anonymous component through Builder.

reference

Uncle Wang is not bald at station B

If this article can help you, it's a great honor. If there are errors and questions in this article, you are welcome to put forward them!

Keywords: Flutter

Added by superhoops on Mon, 03 Jan 2022 05:10:37 +0200