For a complete perception, I advise you to first read the article indicated on the link or refresh it in your memory. In it, I analyzed one of the important aspects of the Flutter device - the interaction of trees and the distribution of responsibilities between them. However, the question remains open: how is the operation of the entire mechanism described in the first part organized? This is what we will try to understand in this article.
General Provisions
If you open the technical overview of Flutter , in one of the points we will see the following diagram. It shows the conditional levels into which the authors of the framework divide it themselves.
In fact, as they called it, we see a puff cake. Larger and smaller layers can be distinguished.
The framework level is everything that we work with at the time of writing the application, and all the utility classes that allow us to interact with the engine level we have written. Everything related to this level is written in Dart.
Engine level - a lower level than the framework level, contains classes and libraries that allow the framework level to work. Including virtual machine Dart, Skia, etc.
Platform level - Platform- specific mechanisms specific to a particular launch platform.
Let's take a closer look at the framework level. In the diagram, it is shown in the form of layers from higher level to low level. At the very bottom we see a layerFoundation . As the name suggests, this layer is something fundamental and basic at the framework level. We find this library and this is what is written in its description:
Core Flutter framework primitives.
The features defined in this library are the lowest-level utility classes and functions used by all the other layers of the Flutter framework.
The functions defined in this library are the utility classes and lowest-level functions used by all other layers of the Flutter framework.
This library also contains the BindingBase - the base class for all Binding.
Binding
First, let's understand what Binding is and how Flutter uses it. The name itself tells us that this is some kind of connection. The documentation left by the Flutter command to BaseBinding tells us the following:
Base class for mixins that provide singleton services (also known as "bindings"). To use this class in an `on` clause of a mixin, inherit from it and implement [initInstances ()]. The mixin is guaranteed to only be constructed once in the lifetime of the app (more precisely, it will assert if constructed twice in checked mode).
This is the base class for various communication services, which are presented as mixins. Each such mixin is initialized and guarantees the uniqueness of its instance during the life of the application.
BaseBindingIs a base abstract class, let's then look at concrete implementations of bindings. Among them we will see:
ServicesBinding is responsible for forwarding messages from the current platform to the message data handler (BinaryMessenger);
PaintingBinding is responsible for communicating with the rendering library.
The RenderBinding is responsible for the communication between the render tree and the Flutter engine.
WidgetBinding is responsible for the communication between the widget tree and the Flutter engine.
SchedulerBinding - the scheduler of the next tasks, such as:
- calls to incoming callbacks that the system initiates in Window.onBeginFrame - for example, events for tickers and animation controllers;
- calls to continuous callbacks that the Window.onDrawFrame system initiates, for example, events to update the display system after the incoming callbacks are completed;
- post-frame callbacks, which are called after continuous callbacks, before returning from Window.onDrawFrame;
- non-rendering tasks that must be performed between frames.
SemanticsBinding is responsible for linking the semantics layer and the Flutter engine.
GestureBinding is responsible for working with the gesture subsystem.
As the name suggests, bindings is a communication layer between the Flutter engine level and the framework level itself, each of which is responsible for a specific direction of work.
WidgetsFlutterBinding
To better understand how it all works together, let's look at the place that is the starting point for any Flutter application - the call to runApp. The method we are calling is in the binding.dart file and this is no coincidence. The description for it says that it expands the passed application widget and attaches it to the screen. Let's see what it does:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
This is where we meet WidgetsFlutterBinding, which is a concrete implementation of an application binding based on a widget framework. At its core, it is the glue that connects the Flutter framework and engine. The WidgetsFlutterBinding consists of many of the bindings we discussed earlier: GestureBinding , ServicesBinding , SchedulerBinding , PaintingBinding , SemanticsBinding , RendererBinding , WidgetsBinding . Thus, we got a layer that can connect our application in all necessary directions with the Flutter engine.
Let's go back to launching the application. In WidgetsFlutterBindingthe scheduleAttachRootWidget and scheduleWarmUpFrame methods are called , let's see what happens in them.
ScheduleAttachRootWidget
The scheduleAttachRootWidget method is a deferred implementation of attachRootWidget. This method belongs to WidgetsBinding . The description for it says that it attaches the passed widget to renderViewElement - the root element of the element tree.
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement);
}
From the code, we can see that this method creates the RenderObjectToWidgetAdapter, which is the root widget of the widget tree and is used as a bridge connecting the trees to each other. Looking at its implementation, we will see that it does not create a RenderObject for itself, but returns a value from the container field, and when we create it, we pass the renderView from the RendererBinding to it. This renderView is the root element of the render tree.
RenderView get renderView => _pipelineOwner.rootNode;
PipelineOwner is actually the manager who manages the rendering process.
Back to the attachRootWidget method... The attachToRenderTree method is called on the created adapter, with which we create the root of the element tree for the first time. Note that buildOwner is passed to the attachToRenderTree method . This class is a widget tree build manager that monitors the state of widgets, monitors the need for updates and performs a number of other important tasks related to updating the state of the widget tree. Thus, we get the very three Flutter trees, each of which is stored and managed through Bindings.
ScheduleWarmUpFrame
The scheduleWarmUpFrame method belongs to the SchedulerBinding and is used to schedule a frame to run as soon as possible without waiting for the "Vsync" system signal. Since the method is used during application startup, the first frame, which is likely to be quite expensive, will take some extra time to fire. This method blocks the dispatch of events until the completion of the scheduled frame.
As we can see, both mechanisms launched at startup relate to and interact with various Bindings, which in turn are closely related and provide interaction with the system. Let's summarize with the following diagram.
Conclusion
Bindings are an important mechanism for organizing a Flutter application. It is he who connects various aspects of the application with each other, as well as with the engine.
Including thanks to him, we can write our application at the highest-level part of the framework, without worrying about how we organize the coherent work of everything else. I hope this article will help you understand this part of the Flutter device.
Thank you for attention!
Materials used:
Flutter
https://flutter.dev/