Flutter, at its core, is a pipeline, which runs a series of phases over, and over, and over. The side-effect of running these phases is the production of your user interface. This article describes the Flutter pipeline at a high level, so that you’re aware of the primary phases and their purpose.
The Flutter pipeline runs the following phases:
- Apply user input (touch events and keyboard input)
- Build trees (widget tree, element tree, render tree, focus tree, semantics tree)
- Layout
- Paint
- Composite
- Update animations
[INSERT IMAGE OF PIPELINE PHASES - MAYBE SHOW IT AS A CIRCLE]
Let’s discuss each phase of the pipeline.
Apply user input
User touch events and key presses are handled so quickly that they might appear to stream into Flutter continuously, but that’s not what’s happening under the hood. The Flutter pipeline includes a specific phase at the beginning of every frame, which processes all user interactions since the previous frame, and runs all code in response.
TODO: check the scheduler phase when receiving touch events in a Listener
widget
User input is applied at the beginning of every frame, so that when the widget tree is built, you have the most up-to-date state to fill your widgets.
After the latest user input is applied, Flutter moves to the build phase.
Build trees
Every Flutter developer is familiar with the widget tree. But Flutter has many trees. Flutter has a render tree, which is responsible for layout, painting, and hit testing. Flutter has an element tree, which connects the widget tree to the render tree. Flutter has a focus tree, which determines where keyboard input goes. And Flutter has a semantics tree that tells accessibility readers what content is on the screen, and how to move through the content.
At the very beginning of a UI, none of these trees exist. Therefore, at some point, these trees need to be built. Moreover, these trees need to be updated every frame, which we refer to as "rebuilding" the trees. To build these trees on the first frame, and to update these trees on every other frame, the Flutter pipeline has a phase called the “build phase”.
The build phase runs after applying user input, and before running layout.
When the build phase is complete, all of the aforementioned trees are constructed and up-to-date.
Layout
Once the build phase is complete, and all of Flutter’s trees have been updated, it’s time to decide what content should appear on the screen, and where it should appear. This process is called layout, which runs in the layout phase.
The layout phase runs after the build phase, and before the paint phase.
Layout behavior is handled entirely by the render tree. The Flutter pipeline tells the root of the render tree to run layout, which then tells its child RenderObject
s to layout, which then tell their child RenderObject
s to run layout, until eventually, all RenderObject
s in the render tree have run layout.
With layout complete, the Flutter pipeline is ready to paint the content in the layout regions.
Paint
The layout phase determines where content should appear on the screen, and how big to make the content. After the layout phase comes the paint phase. The paint phase decides what content should appear in the regions that were calculated during layout.
The paint phase might choose to fill the available space with a rectangle, or a circle, or an image, or text, or a texture, or any combination of these painting artifacts.
Similar to the layout phase, the paint phase is run entirely by the render tree.
But there’s one dirty secret about the paint phase that many developers don’t realize. The paint phase doesn’t actually paint anything. Instead, the paint phase records every draw call that’s made. This means every call to drawRect
, drawOval
, drawParagraph
, etc., are all written down in a massive list called a “display list”.
The display list generated by the paint phase is handed to the next phase, called the composite phase.
Composite
After all the hard work of building trees, laying out rectangles, and painting content, it’s time to finally put that content on screen. This is accomplished by the composite phase.
The composite phase takes the display list generated by the paint phase and pushes it into the Flutter engine (the part written in C++), which then pushes the display list to the GPU to render to textures, and is then finally displayed on the screen.
This means that your Dart code never renders a single pixel. Pixels are only painted through the GPU, as controlled by C++ code in the Flutter engine. Everything you do with Dart, in Flutter, is aimed at configuring that one, massive display list that’s pushed over to the Flutter engine.
Update animations
The last phase of the Flutter pipeline updates animations. This means running any and all callbacks in your UI that are scheduled with Ticker
s and AnimationController
s. Any state changes that result from animation callbacks will be reflected in the widget tree that’s built in the following frame.
Where to go from here
Learn about the Flutter Holy Trinity - the three most important objects in Flutter.
Learn how RenderObject
s work, to layout content, report hit tests, and paint content.
Learn about the layer tree, which holds the final display list structure before the Flutter engine paints the scene.