Last active
July 4, 2025 12:28
-
-
Save nikhil-RGB/4b6ec066f9c1c549bf5c9971f950c8b4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| One of Flutter's most efficient tricks is ability to use a build context to look up widget tree for something of interest. The most common of these are MediaQuery of context and theme of context. But popular state management solutions, like Flutter Block and Provider, is the same mechanism. And the pattern really is accustomed to. Because without it, every live widget that cares about the MediaQuery would have to accept it as a parameter from their parent, meaning every one of their ancestors would have to accept it as a parameter. This would couple every widget in your app to the position in a widget tree. Imagine that refactoring pane. But the pattern is even more useful than merely preventing a thick jungle of parameters. Simply having a widget run the line of code, MediaQuery of context ties the widget to the MediaQuery, meaning it will get rebuilt whenever the MediaQuery changes. And this is important. Because if you are laying out a widget based on the screen size, that layout is unlikely to remain valued if the user rotates their phone or expands their desktop window. But how does this work? Just the right MediaQuery of context registers the widget as a listener to the MediaQuery. Really? Yes. Let's pull back the curtain. First, it's helpful to remember what the ubiquitous context variable actually is. You may recall that Flutter has three primary trees-- the Widget Tree, Element Tree, and RenderObjectTree. Into each widget's build method is passed its corresponding element, which is actually a subclass of builder contexts. So when your widgets assemble themselves with the use of build contexts, it's like talking to a more knowledgeable version of themselves from another dimension. Next, is that often method and the MediaQuery class that we call all the time is a static method that returns a MediaQueryData instance. And internally, it calls context.dependOn InheritedWidgetOfExactType, which can find any inherited widget in constant time. The last line now only returns the MediaQueryData instance we care about, but also registers the widget that called it as a dependency of the MediaQuery. OurWidget element is added to a specialist that MediaQuery will mark for rebuilding whenever it changes. And if you are wondering how other packages like Provider work, it's by doing exactly the same thing. Remember, providers listen parameter. If you set listen to true, Provider calls context.dependOn InheritedWidgetOfExactType. That's how the Provider package is able to watch a provider and automatically reviewed. As a side note, if you set listen to false, Provider calls a similar method, getElementForInh eritedWidgetOfExactType. It's similar, but doesn't create a dependency. Technically, you can call these methods yourself. But that's just the pattern. Here is to use the static of methods we all know so well. Now there is a few catches here. The documentation warns against the calling of context methods during widget constructors or any state. Saying those methods won't get rerun if you do. In fact, the framework will throw an exception if you try to depend on an inherited widget from within the run method. The only safe place to call this method is in state.didChangeDependencies, or build methods. Now, how does this work? If you call this method from init state, somehow the framework knows you cheated, and it won't schedule that widget's init state to be rerun. The answer is that Flutter always knows what phase of the widget lifecycle it currently is. So the static of methods-- first, check to make sure you are in active phase, which contains didChangeDependencies and build. And if so, they inherited the widget's element registers, your widget's element as a dependent. Init state lives in a different phase, which is incompatible with this system. Because initialization only happens once, so it wouldn't make sense to schedule init state to be executed again in the future. Initialization that happens repeatedly would just be Groundhog Day. Initialization that happens repeatedly-- so you can call the static of context methods during didChangeDependencies or during a build method. But what's the difference? You might already be familiar with the build method. But few Flutter developers have spent as much time thinking about didChangeDependencies. First of all, what even are those dependencies its name references? Many developers intuitively assume they are the widget's ancestors, or something. But no, it's the dependencies created by the dependOnInherite dWidgetOfExactType. For example, if you register your widget with the theme, then when the theme changes, your widgets didChangeDependencies method will be called first. Then afterwards, its build method will be called. Deciding where to put your of context method calls involves understanding a few trade-offs. Actually, finding the ancestor widget is cheap. But where you call it only matters if you are doing something expensive with the result. If you only need to run your fancy logic when the theme, or the MediaQuery, or whatever changes, you can consider caching a computed value in the didChangeDependencies, and then referencing it in your build method. And it's worth holding here to truly understand why this works. If you are loading your assets or performing expensive calculations that depend on the inherited widget, then it stands to reason you wouldn't need to re-perform the operation until the inherited widget changes, which is exactly when didChangeDependencies is called. Of course, if you're not doing anything expensive, you can always just put that code right in your build method. This is a standard pattern we all tend to use 99% of the time. To be clear, the performance difference here is very small. But Flutter apps have a lot of widgets and especially during animations when dropped frames are acutely painful. This small difference would add up across your app. If needed, you can also implement your own inherited widgets too, but that's really unnecessary. For more themes, just use Provider. Hopefully, this helps demystify the magic line of code we've all written thousands of times. For more information on Flutter, head to Flutter.dev. [MUSIC PLAYING] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment