DISCLAIMER: This is a sneak peek of what I learned on my Journey to Dive deep into the Flutter Framework. Everything said here was found on the web and in the Flutter framework project on GitHub. If a concept is incorrectly explained, feel free to mention it kindly in the comments section. To dive deeper into all the concepts introduced here, refer to the sources section.
Everything in Flutter is a Widget; it’s the first amendment you must adopt on your journey to becoming a qualified Flutter developer. Widgets help us describe the user interface of our application.
Every Flutter app is, from some perspective, primarily a tree of widgets. The simplicity of its APIs and the variety of its catalogue make widgets the main element of the framework's attractiveness. From the primary widget in the main file to the last icon, each element that appears on the screen is linked by a tree structure.
StatelessWidget is used for part of the user interface that relies only on the properties passed to its constructor. Conversely, StatefulWidget is used for interfaces that can change dynamically or depend on some internal data (aka state).
Talking about StatefulWidget, let's have a quick reminder of the life cycle of a StatefulWidget.
createState: Create the state of the Widget. This method is called whenever a StatefulWidget is being inserted at a location in the widgets tree.
initState: Perform the widget state initialization. This method is called after the widget is inserted into the widgets tree.
didChangeDependencies: Called to notify that at least one of the widget's state dependencies has changed. This method always triggers the rebuild of the widget. It is therefore the ideal place to carry out operations that are too expensive to be included in the body of the build method; and which will be executed at each rebuild.
build: Responsible for building the user interface given a particular location (BuildContext).
At this stage, the Widget is built for the first time. Two methods can trigger a rebuild of the user interface represented by this widget.
setState: This powerful trick helps us rebuild our user interface, to reflect our new state. Internally it notifies the widget that its state object has changed.
didUpdateWidget: This method is called when the widget configuration (e.g. class properties) changes. It helps associate (under certain circumstances) the new widget with the same State object.
A widget's life ends when it is removed from the widget tree, and its resources released.
deactivate: This method is called when the widget is removed from the tree. At this step, the widget can either be reinserted in the tree at another location or removed forever.
activate: This method is called before a widget is reintroduced in the widget tree after its removal.
dispose: It's the last stage. The widget won't be reinserted in the thee, so its resources can be safely released.
The second thing you learn the day you want to dig a little deep under the hood of the framework is that a Widget mainly describes the configuration of an Element. If you take a look at this Element, it is described as the instantiation of a Widget at a particular location in the tree.
An Element is the companion of every widget. It’s responsible for how the view description we provide with our widget is painted and mounted (in our widget tree). It keeps track of where widgets are located in the tree, alongside other widgets.
As we know, widgets are immutable. So when a widget changes (meaning its properties values changed and a rebuild was triggered), there are two cases :
The new widget has the same runtime type and key as the old one: in this case, the element remains associated with the new widget.
Otherwise, the element is removed from our tree, and a new element is created and associated with the new widget. This element can now be mounted in our tree and appear on the screen.
Our Element extends a class that we are more familiar with, BuildContext.
He is soberly described as a handler of the location of a widget in the widget tree. In other words, he helps the widget figure out where it is located in the tree of widgets.
It is an abstract class that exposes useful methods and properties (mounted, size., …) about our widget. Each widget has its own BuildContext.
Because it handles the location of a widget in the tree, the BuildContext value associated with a Widget can change over time to reflect the Widget's new location in the tree.
You may have already encountered the warning, that tries to prevent you from accessing the context after an an asynchronous operation. The only way to check if a context is still associated with a widget (i.e. the widget is still mounted in the tree) is to check the mounted property value.
We have stated recently that Element(s) were responsible for rendering Widgets. We omitted to specify that this task is delegated to RenderObject(s). The name of the Component speaks for itself.
To complete this task, the RenderObject component implements layout and painting protocols. RenderObject associated with the Widget is created when it's been inserted in the tree (the createRenderObject method is called to achieve this).
After the object is created, and attached to a slot in the tree, it calls successively three methods :
The performLayout method helps compute the layout of the widget using constraints information provided by the parent widget.
The paint method provides a visual appearance to the widget given a PaintContext (a place to perform draw operations) and an Offset.
The describeSemanticsConfiguration method helps report semantics associated with this object. It’s generally used for accessibility purposes.
After this operation, the Widget appears on the screen. Another method can be called later: updateRenderObject. He is responsible for updating the render object when the associated widget description changes. This method will eventually call the three precited methods, depending on what values that changed.
So as we stated, there are three trees in every Flutter application :
The Widget Tree: Contains configurations and descriptions of Elements. In fact, for us as developers, it helps us describe how our application interface, must be rendered.
The Element Tree: Handles lifecycles and arranges widgets into a hierarchy. It’s responsible for placing our widget into a location in the Widget tree. It also associates a rendering object with each Widget that needs it.
The RenderObject Tree: Contains information about widget layouts, and sizing and is responsible for painting.
What if I told you, there are more than three trees? Let's see that next time.
In the next blog post, we will dive into Element and explore its lifecycle.