Photo by Jeroen den Otter on Unsplash
Flutter's Trees
A quick explanation of one of Flutter's core internal mechanics
Flutter has three trees:
Widget Tree
Element Tree
Render Tree
You (the developer) with your code, control the Widget Tree. The other two trees are controlled internally by Flutter but are based on your Widget Tree.
Your Widget Tree is just a configuration that rebuilds frequently. It just describes to Flutter what should be outputted on the screen. So it’s just a bunch of config settings.
The Element Tree links the widgets to the actual rendered objects that rarely rebuild.
The Render Tree is a representation of what, at the end, really ends up on the screen. It represents the rendered objects that we see on the screen. This tree is also rarely rebuilt.
For every widget in the Widget Tree, Flutter creates a node (element) for that specific widget in the Element Tree. This element in the end is just an object managed in memory by Flutter, which holds a reference to its widget. It’s just a pointer.
Stateful Widgets are a little bit different. The same things apply, except if you remember we also had the createState()
method in Stateful Widgets. When Flutter encounters a Stateful Widget, it also creates an element (in the Element Tree), but then it also calls the createState()
method and creates a new State
object which is also connected to the element and therefore, indirectly, is related to your widget. The important thing is that the element has a reference to the widget and then it also holds a reference to the State
object. This State
object is independent and not part of the widget. An independent object managed in memory.
Now, I mentioned that these element objects just point to the corresponding widget. However, they also point to the Rendered Box
(the rendered object you see on the screen). Care to guess to which tree this Rendered Box
object belongs? That’s right, the Render Tree. Every element in the Element Tree has a corresponding Rendered Box
in the Render Tree.
Note: When it’s time for Flutter to truly render something to the screen, the process is more complex and there are multiple steps involved. There is a layout phase, then it has a listener setup phase and more.
The build()
method is called by Flutter whenever your state changes. Two important triggers can lead to a rebuild. One is the setState()
method.
Calling setState()
flips the widget’s “dirty bit”. This means that the widget, and therefore its element, has changed. It’s now marked and for the next refresh (which for 60fps happens every 16ms) Flutter will take the new configuration as created by build()
and update the screen.
Since the build()
method runs again and again, you might be thinking about what happens to the other widgets that you might be using inside your widget (widget composition). Do they get instantiated again? The answer is yes. Remember, the terminology we use is ‘widgets’, but in the end, they are just Dart classes. This is by design because the Widget Tree is immutable. An immutable Widget Tree lets Flutter more efficiently detect changes in the tree. Why? Because a new instance of an object has a new address in memory.
The second important trigger that leads to a rebuild is calling MediaQuery.of()
or Theme.of()
methods. If you hover over the methods, your IDE will tell you information about that method and in the first few lines it should mention whether it triggers a rebuild or not. Keep this in mind if you find yourself having constant rebuilds and you don’t know where they’re coming from.
Thank you for reading!