When we start working with Flutter, it's common to delve into the Dart language and get to know the Flutter-provided widgets in detail, leaving behind the understanding of the principles and design concepts that make up this great framework.
This article explores the intricacies of Flutter's architecture, which is ingeniously divided into three primary layers: the Framework, the Engine, and the Embedder. We delve into how each layer operates, from the Framework's high-level APIs for app development to the Engine's core functions like rendering and the Embedder's role in connecting with the operating system. The piece highlights Flutter's declarative programming style, where the user interface is a reflection of the app's state, simplifying the development process. Additionally, it discusses the fundamental elements of the Flutter UI—widgets—and their classifications into StatelessWidget and StatefulWidget, based on their management of state.
The elements of Flutter’s Architecture
The main layers that compose Flutter's architecture are the Framework, the Engine, and the Embedder. Each of these layers is composed of a set of independent libraries. All libraries in the Framework layer have been designed to be optional and replaceable.
Framework
Developed with the Dart programming language, it consists of a series of libraries and layers that handle animations, interactions, rendering, among other components. Developers interact most of their time with this layer.
Engine
It is the core of the framework, mostly written in C++ and designed to be platform-independent. It rasterizes composed scenes whenever a frame needs to be drawn. It provides a low-level implementation of services handling everything from rendering (via Impeller or Skia depending on the platform) to managing connections, animations, and gesture recognition.
Embedder
The embedder coordinates with the host OS for access to different services the app might need. This is platform-specific and has been developed in different languages depending on the OS where the application will run. Using the embedder, existing applications can be integrated as if they were modules.
Anatomy of an App
The following diagram provides an overview of the pieces that make up an application. It shows where the Flutter Engine is in this stack, highlights the API boundaries, and identifies the repositories where individual pieces reside. The legend below clarifies some commonly used terms to describe parts of a Flutter application. (Flutter Documentation).
The official Flutter documentation defines each element as follows:
Dart App:
- Composes widgets into the desired UI.
- Implements business logic.
- Owned by the app developer.
Framework:
- Provides a higher-level API to build high-quality apps (e.g., widgets, hit-testing, gesture detection, accessibility, text input).
- Composites the app’s widget tree into a scene.
Engine:
- Responsible for rasterizing composited scenes.
- Provides a low-level implementation of Flutter’s core APIs (e.g., graphics, text layout, Dart runtime).
- Exposes its functionality to the framework using the dart:ui API.
- Integrates with a specific platform using the Engine’s Embedder API.
Embedder:
- Coordinates with the underlying operating system for access to services like rendering surfaces, accessibility, and input.
- Manages the event loop.
- Exposes a platform-specific API to integrate the Embedder into apps.
Runner:
- Composes the pieces exposed by the platform-specific API of the Embedder into an app package runnable on the target platform.
- Part of the app template generated by ‘flutter create’, owned by the app developer.
UI = f(state)
You have surely heard countless times that the UI is built to reflect the application's state. This is because Flutter was designed as a declarative framework (defining the desired result instead of a series of steps to achieve it). This decision was made to lighten the developers' burden of programming how to transition between different UI states.
In Flutter, developers only need to describe the current state of the UI and leave the transition to the framework.
When the state of your application changes (e.g., when the terms and conditions checkbox is checked), a request is sent to the framework to redraw the UI.
One of the biggest benefits of this programming style is that there is only one path for any UI state.
Widget
The blocks that make up the UI in Flutter are called widgets. A widget defines its user interface by implementing the build() method, a function that transforms the state into the user interface. Buttons, images, and text, for example, are widgets. There are even widgets to define the layout of the application, such as organizing elements in columns or rows. In short, widgets define the structure and behavior of the UI.
Flutter provides two types of widgets: StatelessWidget and StatefulWidget. As the names suggest, a StatelessWidget does not handle state, so it does not maintain any information that needs to be updated, and therefore, the engine does not redraw these widgets when a request to update the UI arrives. On the contrary, a StatefulWidget does handle the state within itself, storing values that can change and are reflected in the UI when calling the setState() method.
Summary
In summary, Flutter's architecture, with its well-defined layers and declarative approach to building the user interface through widgets, gives developers the ability to create efficient and scalable applications. From the high-level logic-handling Framework to the platform-independent Engine and the platform-specific Embedder, Flutter stands out for its simplicity and flexibility. This combination of elements positions Flutter as a standout choice for developing engaging and modern experiences across multiple platforms.