Why is Flutter Fast?— Part 2: Layered Architecture | by Cagatay Ulusoy | Apr, 2022

Source: undraw.co

In this series of articles, we aim to answer the question: “Why Flutter is fast?”. According to Adam Barth who is the co-founder of the Flutter project and the author of the first commit in the Flutter repository, Flutter is fast because it is simple.

Adam Barth in his famous Rendering Pipeline talk

In this part, we will explain Flutter’s layered architecture and its impact on the performance. For a more detailed overview of the architecture, I would suggest reading the famous blog post from the early days of Flutter: The Layer Cake and the best resource regarding this topic on the Flutter website.

What makes an application native is a controversial question. Can a Flutter application be considered a native application? Adam Barth says yes because the widgets in a Flutter app are built with the same technology that the frontend developers are using to build their apps [1].

“Flutter apps are compiled directly to machine code, whether Intel x64 or ARM instructions, or to JavaScript if targeting the web.” [6]

When developing “native” applications, we draw the UI components on screen using the libraries provided by the platform. The platform UI libraries are bridges to the platform rendering APIs such as OpenGL, Metal, Vulkan. These UI libraries are implemented with platform-specific high-level languages ​​for example Java, Swift, Objective-C. They communicate with the device CPU or GPU to draw pixels on the screen.

For example, the widget folder provided by the Android platform is a layer built on top of the view folder. Native Android developers should always consider the minimum supported Android API version for their apps when choosing a widget from this folder.

Considering how late the Android devices get new API updates, this could have been a serious issue for developers. This problem is solved by including the Android Jetpack libraries (formerly known as support libraries) in the application packages to provide backward compatibility across devices with different Android versions. [7]

Traditional cross-platform frameworks are wrappers around either OEM widgets or Web view [1]. In the former case, developers build apps using the native UI components with the help of an abstraction layer. For example, React Native developers write code with JavaScript.

React Native’s bridge system is used to provide bidirectional and asynchronous communication between the native side and the Javascript thread. This is a translation process between one high-level language and many high-level languages. Since the UI logic goes from one place to another through an abstraction layer, there might be unexpected performance issues.

Flutter takes a different approach than other cross-platform technologies. The UI is displayed on the screen using its own libraries instead of the platform UI libraries which eliminates one level of abstraction.

Device GPU is utilized with a graphics library shipped with the application. This enables Flutter to provide 60 or 120 frames per second (fps) performance depending on the device’s capability.

The framework layer of Flutter bypasses the platform UI libraries by directly communicating with Skia in the engine layer which provides instructions for the GPU.

Instead of native UI components, Flutter apps use originally-looking UI components included in the Flutter SDK. Thanks to this approach, the quality and flexibility of the widgets used in Flutter apps are independent of the built-in solutions provided by the underlying platforms.

Developers can use the widgets from the Material Library, Cupertino Library, or fluent_ui package based on the specs from Material Design by Google, Human Interface Guidelines by Apple, and Fluent Design System by Microsoft. Alternatively, developers can also create their own widget set according to their own design language on top of the widgets library.

Flutter has a layered architecture where all the layers are independent, replaceable, and each dependent on the layer below.

Flutter Architectural Layers

Let’s think about the Navigator 2.0 implementation as an example in this layered architectural model. Platforms provide different methods for navigation between screens. Mobile platforms utilize gestures, browsers have backward and forward buttons, and some Android phones have the system back button which people have different ideas about how it should work.

According to the layered model, a generic navigation API is provided in the widgets library of the framework layer without knowing which platform the app is used in. The platform-specific implementation details are included in the engine layer.

Let’s start with what we have in the framework layer. Route is an entry managed by the Navigator widget The Router widget wraps the Navigator widget and configures the navigation history. These classes are all implemented in the widgets library of the framework layer.

The CupertinoPageRoute and MaterialPageRoute classes are two concrete implementations of the Route class which are both used to replace the entire screen with a transition. While the former is part of the cupertino library and provides an iOS-style transition, the latter is part of the material library and provides a platform-adaptive transition according to specs in Material Design. These libraries are built on top of the widgets library.

RouteInformation is a data holder that contains information for a route and it is used in the framework layer internally. The Router widget listens to the route updates coming from the platform channels implemented in the engine layer.

When there is a route update coming from the engine, the Router widget instantiates a RouteInformation to be used in the framework layer. Similarly, the underlying platform may need to know about the navigation updates due to user interactions in the app. For example, in Flutter Web apps, the browser address bar may need to be updated when the content changes due to a menu button press. In this case, the engine gets the necessary information interpreted from a RouteInformation created by the Router widget, and updates its BrowserHistory accordingly.

To sum up, the framework layer depends on the engine layer to build a navigation history. However, it doesn’t need to know about the underlying platform details. For example, pressing the backward/forward buttons in a browser, and receiving an intent from the mobile operating system have different implementations in the engine layer but at the end, the output will be a RouteInformation to be used in the framework layer.

The Flutter engine layer is built with C, C++, and Dart languages. Two main pieces in the architecture that are written in C++ are the 2D graphics library and the text rendering. The main reason for keeping these pieces in C++ is that these solutions have already been in use for many years with Skia for 2D graphics, and with Android Open Source Project (AOSP) libraries for text rendering. This means that the text rendering solution for Android is ported to iOS apps [1].

Skia is an open-source graphics rendering library maintained by Google. Although Flutter is a young technology, Skia has been around since 2005 and is used by many platforms including Google Chrome, Chrome OS, Android, Mozilla Firefox, and more.

Skia in Flutter supports various platform-specific backends that generates instructions for the available GPU in the device. For example, Metal for iOS devices was introduced to the Flutter engine with version 1.17.

“Apple’s support for Metal on iOS provides nearly direct access to the underlying GPU and is Apple’s recommended graphics API. On the iOS devices that fully support Metal, Flutter now uses it by default, making your Flutter apps run faster most of the time, increasing rendering speeds by about 50% on average (depending on your workload).” [8]

The Dart code in the release builds of the Flutter apps is directly compiled into native, ARM, and x86 libraries, or Javascript code when the web is targeted. This compilation process is called ahead-of-time (AOT).

The debug builds of Flutter are compiled just-in-time (JIT) and shipped with the Dart virtual machine (VM). This enables injecting new classes, methods, and fields of the existing classes into the running VM. Hot reload is the process of this injection while keeping the current state of the app [2].

As mentioned in the first article, the Flutter application developer’s responsibility is to describe the state of the UI with a widget tree, and the framework’s responsibility is to update the state with the updateChild method of the elements from top to down in the element tree.

In other words, for developers, everything is a widget, and the framework provides “carefully designed algorithms and data structures to process a large number of widgets efficiencies” [4]. Thanks to this separation of concerns the framework is capable of reflecting the changes in the widget tree immediately in the running application with hot-reload.

Hot reload is like editing the CSS in the Web Browser. Although as of today hot reload is still not supported for Flutter Web apps, it works amazingly well with the rest of the platforms thanks to the declarative paradigm embraced in Flutter [1]. I suggest watching the short video attached below which explains how the hot reload works, and in what cases a hot restart might be needed instead. Kudos to Andrew Fitz for this amazing video with a great visual explanation.

“To the underlying operating system, Flutter applications are packaged in the same way as any other native application.” [2]

Flutter provides the embedder layer when we create a new Flutter project. It is the start point of a Flutter application when launched. It is written in the platform-specific language and hosts the Flutter engine.

Embedder enables communication with the underlying operating system, obtains threads for UI, and provides texture. The responsibilities of the embedder are lifecycle management, input gestures, windows sizing, and platform messages [6].

In the stable channel, we see familiar folder names such as android, ios, weband windows . These are the folders for the embedder layer. If we want to add embedder for macOS and Linux, we should explicitly specify these platforms with the command: flutter create --platforms=windows,macos,linux . Since these platforms are still in the beta channel, the embedder for these platforms is available as a snapshot in the stable channel.

Flutter Architecture for Windows

The embedded layer is included in the Flutter SDK, but it doesn’t have to be restricted to these commonly used platforms. Since Flutter architectural layers are replaceable, a platform-specific custom embedder can be integrated into the rest of the layers. We see flutter-elinux led by Sony for embedded Linux devices, flutter-tizen project for porting Flutter to the devices with Tizen OS as example projects.

Flutter for Embedded Linux (eLinux) — Sony

Web applications are sandboxed in a Web Browser application. Therefore, the Flutter Web architecture doesn’t include an embedder that provides communication with the underlying operating system. For example, we can’t import dart.io libraries to Flutter Web projects. Since the engine layer for other platforms contains logic to interface with the underlying operating system, the engine of the Flutter Web applications is the reimplementation of the C++ Flutter engine on top of standard browser APIs.

In this article, we explained the pixel-driven architecture of Flutter rather than relying on the platform widgets. We mentioned how Flutter eliminates one level of abstraction thanks to its own rendering engine library.

In the next article, we will continue answering the question “Why is Flutter fast?” by explaining the simple rendering pipeline of Flutter.

  1. Flutter with Tim Sneath and Adam Barth, .NET Rocks! podcast
  2. FAQ, docs.flutter.dev
  3. https://developer.android.com/reference/android/view/View
  4. Inside Flutter
  5. Flutter — The sky’s the limit, Swav Kulinski
  6. Flutter Architectural Overview
  7. AndroidX Overview, developer.android.com
  8. Announcing Flutter 1.17

Leave a Comment