diff --git a/.cspell.yaml b/.cspell.yaml index 567e26be..0cb8208f 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -30,6 +30,7 @@ words: - elif - evenodd - ffigen + - jank - libapp - libflutter - libupdater @@ -42,6 +43,8 @@ words: - podfile - prefs - previewable + - recompiles + - riverpod - rollouts - sdkman - shorebirdtech diff --git a/astro.config.mjs b/astro.config.mjs index df61530d..35e314c5 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -80,6 +80,11 @@ export default defineConfig({ collapsed: true, autogenerate: { directory: 'system' }, }, + { + label: 'Flutter Concepts', + collapsed: true, + autogenerate: { directory: 'flutter-concepts' }, + }, ], plugins: [ starlightAutoSidebar(), diff --git a/src/content/docs/flutter-concepts/flutter-sdk-deep-dive.mdx b/src/content/docs/flutter-concepts/flutter-sdk-deep-dive.mdx new file mode 100644 index 00000000..dfe19e34 --- /dev/null +++ b/src/content/docs/flutter-concepts/flutter-sdk-deep-dive.mdx @@ -0,0 +1,407 @@ +--- +title: Flutter SDK Deep Dive +description: Learn how to manage organizations in Shorebird +sidebar: + order: 1 +--- + +This article is a technical deep-dive through various parts of Flutter, written +by Flutter’s founder. It’s not intended to be comprehensive, and there are more +deep dives on [Flutter’s docs site](https://docs.flutter.dev/) and within the +codebase, but it will give you a general high-level overview if you’re +interested in learning more about how Flutter works on the inside. + +# Flutter SDK deep dive: Architecture, tooling, and modern update strategies + +Flutter changed how teams think about cross-platform development, but production +Flutter apps depend on far more than a widget library. The +[Flutter SDK](https://github.com/flutter/flutter) is a complete app runtime and +build system. It includes a rendering engine, a UI framework, and the toolchain +that turns Dart into signed binaries for every platform you ship to. + +Those layers work together to produce fast, native applications. The engine +draws pixels. The framework defines layout and interaction. The tooling +compiles, packages, and signs your code so it can run on iOS, Android, web, and +desktop. + +What the SDK does not provide is a modern way to update those applications once +they are in users’ hands. Flutter’s default delivery model still revolves around +static binaries and app store submissions, which means every fix, no matter how +small, moves at the pace of store review cycles. For teams shipping frequently, +that gap becomes the bottleneck. + +This guide will walk you through what lives inside the Flutter SDK, how those +pieces fit together, and how [Shorebird](https://shorebird.dev/) extends it with +a production-ready update path for real-world release cycles. + +## Dart's dual compilation gives developers the best of both worlds + +Dart is uncommon in that it supports both Just-In-Time (JIT) and Ahead-Of-Time +(AOT) compilation, each serving distinct purposes in the development lifecycle. +During development, the Dart VM runs in JIT mode, compiling code at runtime and +enabling the sub-second hot reload that makes Flutter development remarkably +productive. For production, Dart's AOT compiler transforms your entire codebase +into native ARM or x64 machine code, eliminating runtime compilation overhead +and delivering consistently fast startup times. + +The JIT compiler's key capability is _incremental recompilation_. When you save +a file, only modified functions are recompiled and injected into the running VM +while preserving application state. Variables, animations, and scroll positions +persist through reloads. Hot Restart, triggered with a capital `R`, differs +fundamentally: it destroys the current widget tree, creates a new Dart isolate, +and re-executes from `main()`, resetting all state in seconds versus hot +reload's sub-second updates. + +AOT compilation runs dart compile, or under the covers, the `gen_snapshot` tool, +which performs global analysis from your `main()` entry point, applies tree +shaking to eliminate unreachable code, and generates platform-specific binaries. +This produces fast startup, small binaries, and code that's harder to +reverse-engineer, but creates a critical limitation: _unlike JIT, an AOT binary +cannot be updated without rebuilding and redistributing, which on mobile means +long app store waits._ + +## Flutter Engine and the shift from Skia to Impeller + +Another part of Flutter I’m commonly asked about is why Flutter has its own +rendering pipeline, Impeller. + +The Flutter Engine is a portable C++ runtime providing the rendering pipeline, +Dart VM integration, and platform abstraction layer. Historically, Flutter used +[Skia](https://skia.org/), the same 2D graphics library powering Chrome and +Android, for GPU-accelerated rendering. However, Skia's architecture, like +Dart’s development mode, is based on a just-in-time architecture. This +architecture is built for web browsers, where each page is different and might +need different shaders to produce its graphics. On mobile, this just-in-time +compilation is not how best-in-class graphics are done, and it created a +persistent problem: shader compilation jank. + +When Skia encounters new graphical elements (complex gradients, blur effects, +custom shaders), it compiles GPU shaders at runtime. This compilation can +consume hundreds of milliseconds (even more on iOS, where compilation must be +done out-of-process) when a smooth 60fps animation requires each frame to +complete in 16ms. Users would experience visible stuttering during first-time +animations, a problem that couldn't be solved through optimization alone. + +Impeller represents Flutter's ground-up solution, designed specifically for +Flutter's rendering patterns. The key innovation is AOT shader compilation: all +shaders are compiled at build time, not runtime, eliminating compilation jank +entirely. As of Flutter 3.27, [Impeller](https://docs.flutter.dev/perf/impeller) +is the default renderer on iOS (with no fallback option) and Android API 29+, +falling back to Skia on older devices. +[Benchmarks show](https://medium.com/@raiden.lpf666/skia-vs-impeller-a-performance-comparison-e1c7dfd9e861) +a 30% reduction in average GPU raster time and over 70% fewer dropped frames in +animation-heavy applications. + +## The three-tree architecture powers efficient UI updates + +Another question I’m sometimes asked is how the Flutter interaction pipeline +works. + +Flutter's framework layer maintains three parallel tree structures that work +together for efficient rendering: + +| Tree | Purpose | Characteristics | +| --------------------- | ----------------------------- | ------------------------------------------ | +| **Widget Tree** | Declarative UI blueprint | Immutable, lightweight, frequently rebuilt | +| **Element Tree** | Runtime lifecycle management | Mutable, performs reconciliation (diffing) | +| **RenderObject Tree** | Layout, painting, hit-testing | Expensive to create, handles actual pixels | + +The Render tree works like Views in other systems, or the DOM in the web. +Mutable, large objects that know how to render themselves but can be tricky to +orchestrate, or keep synced with the state of the app. + +The Widget tree works like React.js and other popular reactive frameworks do. +It’s essentially immutable templates for what you want your UI to look like, +that are cheap to build and tear down and can be stamped out every frame. Data +flow through widgets is unidirectional, you just make new widgets when you want +to change the UI, you don’t need to worry about updating existing ones. + +And finally, the Element tree (the BuildContext your `build()` method is passed) +is the glue that holds these two worlds together. It’s responsible for managing +the lifetimes of render objects and keeping them updated as Widgets come and go. + +Here’s what the render process looks like: + +[](https://mermaid.live/edit#pako:eNplVNtO4zAQ_ZWRJaRdUdg20Fu0QtqWCK1UStUWKm2yD04yNF5SO7IdoFz-HWfSC2XzkvHczpnjSV5ZolJkPrvP1VOScW1hPogkwNERBNLqNRRKSGsq1214a1CDVWWSoQGTaET5t4pMwkmV5YL4iNKS7yq8QmNLjb80Sv4z1j8uNBqVPyJoTNRSihfUhlKHYcQ2yZDwPI958mCo4puSc140QMlZolWef48YVcxCg3Zmua0LMAUh4dKxp-ggDHJcOSIOKS5FnjrvZqbfsigtzE6SUj8iDQUnJxcwoSnIvCLyZA6JHJkzQiVzsGs2qHpDkXFDrRbhGJ9gIdIlWgNOHUdvC23KeKl5kcFiHtYZMHfyEV1XWb1QpnXugGAWO5ipk0smIhfcCkXOILzmNsngqcYiqeI12HWBcAwPuP6KGsx3knyCDQ5gFwQb7KfTXCaZkEtCvA2nWBqETRvqEIxp4r2LnFWbN-L35sr2rrGC1cY7_jSbTN3aqPgfJvWWTd2aFWl1s3XshkIg7kEipiSoSxqHQ9L3IGnLoL7TaY09rg__QRaiwFxIurlROOJr5RbjGAruFpkEPYZMWLBuL50GXwWdzsMDep9UHR2oOq3JjGrWW3tD5aa0bh2rw104Ec-YG7fp-w-LqFHJXSRZgy21SJlvdYkNtkK94tWRvVZpEbOZu4WI-c6Mq4VkkXx3NQWXf5Rabcu0KpcZ8-95btypJKEvBXdDrXZeTYMNVSkt8zttj5ow_5U9M791dnba9Xr9Zqvldb1uq8HWzPe803671-13Wt3zZu-s03tvsBcCbZ722ucdr913j9dte81Og2EqrNLX9W-H_j7vH01hcuE) + +When `setState()` triggers a rebuild, the Element tree compares new widgets with +existing ones using type and key matching. If they match, the Element is reused +and only the `RenderObject` is updated, avoiding expensive recreation. The +`BuildContext` passed to every `build()` method is actually the `Element` +itself, wrapped in an interface. This reliance on fast comparison between new +and old explains why widget identity and why `const` constructors improve +performance by enabling widget reuse. + +Flutter’s gesture system is also somewhat innovative, in that “which gesture +applies” to a set of inputs is decided locally amongst various “recognizers” +rather than through a global set of hard-coded if/else statements. The gesture +system operates through a +[_GestureArena_](https://www.droidcon.com/2024/10/17/how-to-be-a-gladiator-in-the-gesture-arena/) +that resolves conflicts when multiple recognizers compete for the same pointer +sequence. Each recognizer can claim victory (accept) or bow out (reject), with +the first to complete winning exclusive handling. This helps make it easy to +build complex, composable interactions with Flutter Widgets, including making +nested scrollable areas and overlapping tap targets behave predictably even when +appearing in different contexts, without having to edit other parts of the +framework. + +## The embedder bridges Flutter to native platforms + +Flutter, as portable as it is, isn’t enough alone. Flutter apps run within an +operating system and interact with other applications and libraries. To do that, +Flutter provides a variety of bridging mechanisms to take care of most of this +for you, or let you access the rest of the device when you need. + +The embedder layer is the platform-specific native application that hosts +Flutter content. Written in Java/C++ for Android, Swift/Objective-C for iOS, and +C++ for desktop platforms, embedders provide the entry point, rendering surface, +event loop, and thread management. + +For Android, Flutter runs as an Activity with `FlutterView` rendering content. +iOS hosts Flutter in a `FlutterViewController` using Metal for rendering. +[Platform channels](https://docs.flutter.dev/platform-integration/platform-channels) +enable communication between Dart and native code through three patterns: + +- **MethodChannel**: Request-response calls to native methods +- **EventChannel**: Streaming data from native to Dart (sensors, real-time + updates) +- **BasicMessageChannel**: Bidirectional asynchronous messaging + +For even lower-level interop, +[FFI (Foreign Function Interface)](https://dart.dev/guides/libraries/c-interop) +provides synchronous calls to C-compatible code with better performance but +increased complexity. + +## Hot reload mechanics depend on Dart VM code injection + +One of the first things that Flutter was noticed for was Hot Reload. Fluter’s +hot-reload provides you with sub-second, on-device updates to your running app +while you’re editing your code. Pulling that off is no small feat. + +