From 4026fb9755987fd2b0f147ba5d4505fc5982a477 Mon Sep 17 00:00:00 2001 From: dawn-ducky Date: Tue, 20 Jan 2026 11:12:42 -0600 Subject: [PATCH 1/6] Create new section in docs for Flutter concepts and add first article This article provides a comprehensive technical deep-dive into the Flutter SDK, covering its architecture, tooling, and update strategies. It discusses Dart's compilation methods, the Flutter engine's rendering pipeline, and introduces Shorebird for over-the-air updates. --- .../Flutter-concepts/Flutter-SDK-deep-dive.md | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md diff --git a/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md b/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md new file mode 100644 index 00000000..7cf3a122 --- /dev/null +++ b/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md @@ -0,0 +1,172 @@ + + +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.ink/img/pako:eNplVNtO4zAQ_ZWRJaRdUdg20Fu0QtqWCK1UStUWKm2yD04yNF5SO7IdoFz-HWfSC2XzkvHczpnjSV5ZolJkPrvP1VOScW1hPogkwNERBNLqNRRKSGsq1214a1CDVWWSoQGTaET5t4pMwkmV5YL4iNKS7yq8QmNLjb80Sv4z1j8uNBqVPyJoTNRSihfUhlKHYcQ2yZDwPI958mCo4puSc140QMlZolWef48YVcxCg3Zmua0LMAUh4dKxp-ggDHJcOSIOKS5FnjrvZqbfsigtzE6SUj8iDQUnJxcwoSnIvCLyZA6JHJkzQiVzsGs2qHpDkXFDrRbhGJ9gIdIlWgNOHUdvC23KeKl5kcFiHtYZMHfyEV1XWb1QpnXugGAWO5ipk0smIhfcCkXOILzmNsngqcYiqeI12HWBcAwPuP6KGsx3knyCDQ5gFwQb7KfTXCaZkEtCvA2nWBqETRvqEIxp4r2LnFWbN-L35sr2rrGC1cY7_jSbTN3aqPgfJvWWTd2aFWl1s3XshkIg7kEipiSoSxqHQ9L3IGnLoL7TaY09rg__QRaiwFxIurlROOJr5RbjGAruFpkEPYZMWLBuL50GXwWdzsMDep9UHR2oOq3JjGrWW3tD5aa0bh2rw104Ec-YG7fp-w-LqFHJXSRZgy21SJlvdYkNtkK94tWRvVZpEbOZu4WI-c6Mq4VkkXx3NQWXf5Rabcu0KpcZ8-95btypJKEvBXdDrXZeTYMNVSkt8zttj5ow_5U9M791dnba9Xr9Zqvldb1uq8HWzPe803671-13Wt3zZu-s03tvsBcCbZ722ucdr913j9dte81Og2EqrNLX9W-H_j7vH01hcuE?type=png)](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. + +
+ +
+ + +Understanding hot reload's technical implementation reveals its capabilities and limitations. When you press `r`, the host machine scans for changed code, recompiles affected libraries plus the main library, generates Dart kernel files (an intermediate representation), and sends them to the device's Dart VM. The VM reloads libraries, and the Flutter framework triggers a complete rebuild and repaint of existing widgets. + +The [Dart VM wiki](https://github.com/dart-lang/sdk/blob/main/runtime/docs/hot-reload.md) describes this as *pervasive late-binding*: the program behaves as if method lookup happens at every call site. However, field values are preserved; changing an initializer doesn't affect already-initialized variables. Closures capture their function at creation time and won't pick up changes. + +There are some changes that Hot Reload cannot handle, and a Hot Restart will be required: +- Enum-to-class conversions or generic type modifications +- Changes to `main()` or `initState()` +- Static field initializer changes +- Non-Dart-code changes (e.g., native code) (always require a full app restart) + +## Testing spans three tiers with distinct tradeoffs + +Another question that comes up sometimes is how one should think about testing one’s Flutter app. After some trial and error, the Flutter framework itself landed on a three-tiered testing pyramid principle: many fast unit tests, fewer widget tests, and minimal integration tests. Golden tests are also often thrown in there too, and should be used with caution. + +**Unit tests** use the [test](https://pub.dev/packages/test) package to verify isolated functions and classes. External dependencies should be mocked using packages like [Mockito](https://pub.dev/packages/mockito). These execute in milliseconds and catch logic errors early. I often find it particularly helpful to separate my app into “ui” (Flutter) code and “logic” pure Dart code, and extensively test the logic with unit tests. + +**Widget tests** use [flutter_test](https://api.flutter.dev/flutter/flutter_test/) and the `WidgetTester` class to render widgets without a physical device. Key methods include `pumpWidget()` to build the widget tree, `pump()` to advance by one frame after state changes, and `pumpAndSettle()` to wait for all animations to complete. The `find` API locates widgets by text, type, key, or icon, while matchers like `findsOneWidget` and `findsNothing` verify results. These are very useful for testing complex widgets quickly. They’re essentially Flutter-level unit tests. + +**Golden Tests** are a controversial, but common, testing mechanism where the exact pixels from your app are recorded and checked against past recordings. It’s very effective at catching unintentional visual bugs before they reach users, but they can be extremely painful to maintain across multiple platforms and configurations. There are a variety of 3p providers who can help make this easier, including WidgetBook.com. + +**Integration tests** use the [integration_test](https://docs.flutter.dev/testing/integration-tests) package (which replaced `flutter_driver`) to test complete app flows on real devices. Tests run through `IntegrationTestWidgetsFlutterBinding` and can execute on [Firebase Test Lab](https://firebase.google.com/docs/test-lab) for device farm testing. Patrol is an alternative 3p approach as well. In general, integration tests are the most expensive to write, maintain, and run, and should be used sparingly. + +## AOT compilation creates the immutability problem + +As mentioned above, one of the limitations in stock Flutter is that once you’ve built a binary for distribution, you are left at the whims of each platform. Not anymore. This is a problem Shorebird.dev has solved for you. + +The build process using `flutter build apk` or `flutter build ipa` invokes AOT compilation, transforming Dart source into native machine code. Tree shaking removes unreachable code, [R8](https://developer.android.com/studio/build/shrink-code) shrinks Java/Kotlin code on Android, and the final binary is signed for distribution. Build times typically range from **3-8 minutes for Android** and **10-25 minutes for iOS** (with many common dependencies, including Firebase, often adding significant overhead). + +Unlike React Native, where JavaScript bundles can be updated at runtime through services like [Microsoft's CodePush](https://microsoft.github.io/code-push/), Flutter's compiled Dart code is static machine code with no runtime interpreter in release builds. Once published, fixing bugs requires a full app store submission, typically taking days. + +That immutability shows up everywhere in day-to-day delivery. Every fix, no matter how small, has to flow back through the full build and release pipeline. New binaries must be produced, signed, and submitted for each platform before users see the change. What makes this painful is not just store review time, but the fact that there is no smaller unit of deployment. You cannot ship a patch, a hotfix, or a targeted change. You can only ship a whole new app. + +## Shorebird enables over-the-air Dart code updates + +This release pain is one of the reasons we started Shorebird. [Shorebird](https://shorebird.dev/), solves the distribution problem through sophisticated engine modifications. The platform maintains forks of Flutter and Dart. When you install Shorebird, it provides custom Flutter and Dart copies that produce [Shorebird-enabled binaries](https://docs.shorebird.dev/code-push/). + +Apple's developer agreement requires interpreted code for OTA updates, prohibiting JIT compilation. To provide updates, Shorebird built a *custom Dart interpreter*. To maintain Flutter’s high-performance, while still providing interpreter-updates, Shorebird also introduced a novel linker phase, which allows the vast majority of your app to still run out of the AOT compiled binary, rather than interpreting after update. This linker analyzes two Dart programs (release and patch), finds maximal similarity, and determines per-function whether to use the original binary or interpreter. Typically, 98%+ of patched code runs from the original binary at full speed. + +The [workflow](https://docs.shorebird.dev/code-push/guides/development-workflow/) integrates seamlessly with existing development, with no required changes to your application: + +```bash +shorebird release android # Build and register release +# Submit to app stores... +# Fix bug in Dart code... +shorebird patch android # Create and deploy patch +``` + +[Patches](https://docs.shorebird.dev/code-push/patch/) use **binary diffing**. On Android, patches are typically a few kilobytes. iOS patches tend to be hundreds of kilobytes. Users receive patches on their next app restart, with automatic [rollback protection](https://docs.shorebird.dev/code-push/rollback/) if a patch fails to launch. + +Critically, Shorebird helps you maintain app store compliance: patches only modify Dart code (not native code, Flutter engine, or assets). + +## Current best practices for Flutter development in 2026 + +**Flutter 3.38.5** (December 2025) is the current stable release, bundled with **Dart 3.10.4**. Key requirements now include Java 17 minimum for Android, iOS 13+ minimum, and Android 16KB page size support for Google Play compliance. Impeller is fully default across iOS and Android API 29+. + +For static analysis, configure `analysis_options.yaml` to include `package:flutter_lints/flutter.yaml` and enable strict mode: + +```yaml +analyzer: + language: + strict-casts: true + strict-inference: true +``` + +We recommend running `flutter analyze` before commits and `dart fix --apply` to automatically resolve deprecated API usage. For deeper analysis, [DCM (Dart Code Metrics)](https://dcm.dev/) provides complexity metrics and unused code detection. Also, use shorebird for testing and deploying patches quickly to your released app. + +State management in 2025-2026 favors *[Riverpod 3](https://riverpod.dev/)* for new projects (compile-time safety, modular architecture), *[Bloc](https://bloclibrary.dev/)* for enterprise applications requiring strict separation of concerns, and *[Provider](https://pub.dev/packages/provider)* for simpler applications. [Flutter Signals](https://pub.dev/packages/signals) has emerged as an option for local reactive state. + +Adopt a feature-first project structure for medium-to-large applications, splitting code into `core/`, `features/`, and `services/` directories with each feature containing its own data, domain, and presentation layers. Use `const` constructors liberally, keep widget trees shallow, and profile regularly with [DevTools](https://docs.flutter.dev/tools/devtools/overview). + +## Conclusion + +Flutter's architecture delivers native performance through AOT compilation while maintaining development velocity through JIT-powered hot reload. Flutter’s custom Impeller rendering pipeline provides reliably smooth 60fps animations. The three-tree rendering architecture and platform embedder design enable true cross-platform code sharing without sacrificing native integration. + +The most significant evolution for production Flutter teams is [Shorebird's code push capability](https://docs.shorebird.dev/code-push/). By building a custom interpreter and novel per-function linker, [Shorebird solves the immutability problem](https://docs.shorebird.dev/code-push/system-architecture/) while maintaining app store compliance, enabling bug fixes in hours rather than days. Combined with [Shorebird CI's](https://docs.shorebird.dev/ci/) zero-config testing and modern static analysis practices, Flutter teams can ship confidently while retaining the ability to respond quickly to production issues. + +## Next Steps + +Once you understand Flutter's architecture and update strategies, consider these resources to implement production-ready workflows: + +**Set up code push for your app**: Start with the [Shorebird Quick Start Guide](https://docs.shorebird.dev/getting-started/) to enable over-the-air updates in your Flutter application. The guide covers installation, initialization, and creating your first release in under 15 minutes. + +**Establish a deployment workflow**: Review the [Development Workflow Guide](https://docs.shorebird.dev/code-push/guides/development-workflow/) to understand best practices for managing releases and patches. Learn when to create patches versus new releases, how to test updates before deployment, and strategies for [staging patches](https://docs.shorebird.dev/code-push/guides/staging-patches/) to specific user groups. + +**Implement gradual rollouts**: Use [percentage-based rollouts](https://docs.shorebird.dev/code-push/guides/percentage-based-rollouts/) to validate that patched Dart code behaves correctly on real devices before it replaces the AOT-compiled logic for everyone. This lets you verify that a hotfix works across hardware, OS versions, and app states without committing every user to a full store update. + +**Secure your deployment pipeline**: Enable [patch signing](https://docs.shorebird.dev/code-push/guides/patch-signing/) so that only code produced by your release pipeline can be executed in place of the original AOT binary. This preserves the same trust model as app-store signing, even though code is now being delivered outside of the store. + + + + From 3a4436b2324be1a5734dfcd61d35ea1ae1287e7e Mon Sep 17 00:00:00 2001 From: Tom Arra Date: Tue, 20 Jan 2026 11:28:04 -0600 Subject: [PATCH 2/6] gertting things working --- astro.config.mjs | 5 + .../Flutter-concepts/Flutter-SDK-deep-dive.md | 399 ++++++++++++++---- .../Flutter-SDK-deep-dive.mdx | 399 ++++++++++++++++++ 3 files changed, 717 insertions(+), 86 deletions(-) create mode 100644 src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.mdx 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.md b/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md index 7cf3a122..c783bfc8 100644 --- a/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md +++ b/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md @@ -1,89 +1,204 @@ - - -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. +--- +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. +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. +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. +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. +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.* +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. +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. +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: +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) | +| 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 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. +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. +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.ink/img/pako:eNplVNtO4zAQ_ZWRJaRdUdg20Fu0QtqWCK1UStUWKm2yD04yNF5SO7IdoFz-HWfSC2XzkvHczpnjSV5ZolJkPrvP1VOScW1hPogkwNERBNLqNRRKSGsq1214a1CDVWWSoQGTaET5t4pMwkmV5YL4iNKS7yq8QmNLjb80Sv4z1j8uNBqVPyJoTNRSihfUhlKHYcQ2yZDwPI958mCo4puSc140QMlZolWef48YVcxCg3Zmua0LMAUh4dKxp-ggDHJcOSIOKS5FnjrvZqbfsigtzE6SUj8iDQUnJxcwoSnIvCLyZA6JHJkzQiVzsGs2qHpDkXFDrRbhGJ9gIdIlWgNOHUdvC23KeKl5kcFiHtYZMHfyEV1XWb1QpnXugGAWO5ipk0smIhfcCkXOILzmNsngqcYiqeI12HWBcAwPuP6KGsx3knyCDQ5gFwQb7KfTXCaZkEtCvA2nWBqETRvqEIxp4r2LnFWbN-L35sr2rrGC1cY7_jSbTN3aqPgfJvWWTd2aFWl1s3XshkIg7kEipiSoSxqHQ9L3IGnLoL7TaY09rg__QRaiwFxIurlROOJr5RbjGAruFpkEPYZMWLBuL50GXwWdzsMDep9UHR2oOq3JjGrWW3tD5aa0bh2rw104Ec-YG7fp-w-LqFHJXSRZgy21SJlvdYkNtkK94tWRvVZpEbOZu4WI-c6Mq4VkkXx3NQWXf5Rabcu0KpcZ8-95btypJKEvBXdDrXZeTYMNVSkt8zttj5ow_5U9M791dnba9Xr9Zqvldb1uq8HWzPe803671-13Wt3zZu-s03tvsBcCbZ722ucdr913j9dte81Og2EqrNLX9W-H_j7vH01hcuE?type=png)](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. +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. +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. +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: +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) +- **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. +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. +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.
+Understanding hot reload's technical implementation reveals its capabilities and +limitations. When you press `r`, the host machine scans for changed code, +recompiles affected libraries plus the main library, generates Dart kernel files +(an intermediate representation), and sends them to the device's Dart VM. The VM +reloads libraries, and the Flutter framework triggers a complete rebuild and +repaint of existing widgets. -Understanding hot reload's technical implementation reveals its capabilities and limitations. When you press `r`, the host machine scans for changed code, recompiles affected libraries plus the main library, generates Dart kernel files (an intermediate representation), and sends them to the device's Dart VM. The VM reloads libraries, and the Flutter framework triggers a complete rebuild and repaint of existing widgets. +The +[Dart VM wiki](https://github.com/dart-lang/sdk/blob/main/runtime/docs/hot-reload.md) +describes this as _pervasive late-binding_: the program behaves as if method +lookup happens at every call site. However, field values are preserved; changing +an initializer doesn't affect already-initialized variables. Closures capture +their function at creation time and won't pick up changes. -The [Dart VM wiki](https://github.com/dart-lang/sdk/blob/main/runtime/docs/hot-reload.md) describes this as *pervasive late-binding*: the program behaves as if method lookup happens at every call site. However, field values are preserved; changing an initializer doesn't affect already-initialized variables. Closures capture their function at creation time and won't pick up changes. +There are some changes that Hot Reload cannot handle, and a Hot Restart will be +required: -There are some changes that Hot Reload cannot handle, and a Hot Restart will be required: - Enum-to-class conversions or generic type modifications - Changes to `main()` or `initState()` - Static field initializer changes @@ -91,33 +206,96 @@ There are some changes that Hot Reload cannot handle, and a Hot Restart will be ## Testing spans three tiers with distinct tradeoffs -Another question that comes up sometimes is how one should think about testing one’s Flutter app. After some trial and error, the Flutter framework itself landed on a three-tiered testing pyramid principle: many fast unit tests, fewer widget tests, and minimal integration tests. Golden tests are also often thrown in there too, and should be used with caution. - -**Unit tests** use the [test](https://pub.dev/packages/test) package to verify isolated functions and classes. External dependencies should be mocked using packages like [Mockito](https://pub.dev/packages/mockito). These execute in milliseconds and catch logic errors early. I often find it particularly helpful to separate my app into “ui” (Flutter) code and “logic” pure Dart code, and extensively test the logic with unit tests. - -**Widget tests** use [flutter_test](https://api.flutter.dev/flutter/flutter_test/) and the `WidgetTester` class to render widgets without a physical device. Key methods include `pumpWidget()` to build the widget tree, `pump()` to advance by one frame after state changes, and `pumpAndSettle()` to wait for all animations to complete. The `find` API locates widgets by text, type, key, or icon, while matchers like `findsOneWidget` and `findsNothing` verify results. These are very useful for testing complex widgets quickly. They’re essentially Flutter-level unit tests. - -**Golden Tests** are a controversial, but common, testing mechanism where the exact pixels from your app are recorded and checked against past recordings. It’s very effective at catching unintentional visual bugs before they reach users, but they can be extremely painful to maintain across multiple platforms and configurations. There are a variety of 3p providers who can help make this easier, including WidgetBook.com. - -**Integration tests** use the [integration_test](https://docs.flutter.dev/testing/integration-tests) package (which replaced `flutter_driver`) to test complete app flows on real devices. Tests run through `IntegrationTestWidgetsFlutterBinding` and can execute on [Firebase Test Lab](https://firebase.google.com/docs/test-lab) for device farm testing. Patrol is an alternative 3p approach as well. In general, integration tests are the most expensive to write, maintain, and run, and should be used sparingly. +Another question that comes up sometimes is how one should think about testing +one’s Flutter app. After some trial and error, the Flutter framework itself +landed on a three-tiered testing pyramid principle: many fast unit tests, fewer +widget tests, and minimal integration tests. Golden tests are also often thrown +in there too, and should be used with caution. + +**Unit tests** use the [test](https://pub.dev/packages/test) package to verify +isolated functions and classes. External dependencies should be mocked using +packages like [Mockito](https://pub.dev/packages/mockito). These execute in +milliseconds and catch logic errors early. I often find it particularly helpful +to separate my app into “ui” (Flutter) code and “logic” pure Dart code, and +extensively test the logic with unit tests. + +**Widget tests** use +[flutter_test](https://api.flutter.dev/flutter/flutter_test/) and the +`WidgetTester` class to render widgets without a physical device. Key methods +include `pumpWidget()` to build the widget tree, `pump()` to advance by one +frame after state changes, and `pumpAndSettle()` to wait for all animations to +complete. The `find` API locates widgets by text, type, key, or icon, while +matchers like `findsOneWidget` and `findsNothing` verify results. These are very +useful for testing complex widgets quickly. They’re essentially Flutter-level +unit tests. + +**Golden Tests** are a controversial, but common, testing mechanism where the +exact pixels from your app are recorded and checked against past recordings. +It’s very effective at catching unintentional visual bugs before they reach +users, but they can be extremely painful to maintain across multiple platforms +and configurations. There are a variety of 3p providers who can help make this +easier, including WidgetBook.com. + +**Integration tests** use the +[integration_test](https://docs.flutter.dev/testing/integration-tests) package +(which replaced `flutter_driver`) to test complete app flows on real devices. +Tests run through `IntegrationTestWidgetsFlutterBinding` and can execute on +[Firebase Test Lab](https://firebase.google.com/docs/test-lab) for device farm +testing. Patrol is an alternative 3p approach as well. In general, integration +tests are the most expensive to write, maintain, and run, and should be used +sparingly. ## AOT compilation creates the immutability problem -As mentioned above, one of the limitations in stock Flutter is that once you’ve built a binary for distribution, you are left at the whims of each platform. Not anymore. This is a problem Shorebird.dev has solved for you. - -The build process using `flutter build apk` or `flutter build ipa` invokes AOT compilation, transforming Dart source into native machine code. Tree shaking removes unreachable code, [R8](https://developer.android.com/studio/build/shrink-code) shrinks Java/Kotlin code on Android, and the final binary is signed for distribution. Build times typically range from **3-8 minutes for Android** and **10-25 minutes for iOS** (with many common dependencies, including Firebase, often adding significant overhead). - -Unlike React Native, where JavaScript bundles can be updated at runtime through services like [Microsoft's CodePush](https://microsoft.github.io/code-push/), Flutter's compiled Dart code is static machine code with no runtime interpreter in release builds. Once published, fixing bugs requires a full app store submission, typically taking days. - -That immutability shows up everywhere in day-to-day delivery. Every fix, no matter how small, has to flow back through the full build and release pipeline. New binaries must be produced, signed, and submitted for each platform before users see the change. What makes this painful is not just store review time, but the fact that there is no smaller unit of deployment. You cannot ship a patch, a hotfix, or a targeted change. You can only ship a whole new app. +As mentioned above, one of the limitations in stock Flutter is that once you’ve +built a binary for distribution, you are left at the whims of each platform. Not +anymore. This is a problem Shorebird.dev has solved for you. + +The build process using `flutter build apk` or `flutter build ipa` invokes AOT +compilation, transforming Dart source into native machine code. Tree shaking +removes unreachable code, +[R8](https://developer.android.com/studio/build/shrink-code) shrinks Java/Kotlin +code on Android, and the final binary is signed for distribution. Build times +typically range from **3-8 minutes for Android** and **10-25 minutes for iOS** +(with many common dependencies, including Firebase, often adding significant +overhead). + +Unlike React Native, where JavaScript bundles can be updated at runtime through +services like [Microsoft's CodePush](https://microsoft.github.io/code-push/), +Flutter's compiled Dart code is static machine code with no runtime interpreter +in release builds. Once published, fixing bugs requires a full app store +submission, typically taking days. + +That immutability shows up everywhere in day-to-day delivery. Every fix, no +matter how small, has to flow back through the full build and release pipeline. +New binaries must be produced, signed, and submitted for each platform before +users see the change. What makes this painful is not just store review time, but +the fact that there is no smaller unit of deployment. You cannot ship a patch, a +hotfix, or a targeted change. You can only ship a whole new app. ## Shorebird enables over-the-air Dart code updates -This release pain is one of the reasons we started Shorebird. [Shorebird](https://shorebird.dev/), solves the distribution problem through sophisticated engine modifications. The platform maintains forks of Flutter and Dart. When you install Shorebird, it provides custom Flutter and Dart copies that produce [Shorebird-enabled binaries](https://docs.shorebird.dev/code-push/). - -Apple's developer agreement requires interpreted code for OTA updates, prohibiting JIT compilation. To provide updates, Shorebird built a *custom Dart interpreter*. To maintain Flutter’s high-performance, while still providing interpreter-updates, Shorebird also introduced a novel linker phase, which allows the vast majority of your app to still run out of the AOT compiled binary, rather than interpreting after update. This linker analyzes two Dart programs (release and patch), finds maximal similarity, and determines per-function whether to use the original binary or interpreter. Typically, 98%+ of patched code runs from the original binary at full speed. - -The [workflow](https://docs.shorebird.dev/code-push/guides/development-workflow/) integrates seamlessly with existing development, with no required changes to your application: +This release pain is one of the reasons we started Shorebird. +[Shorebird](https://shorebird.dev/), solves the distribution problem through +sophisticated engine modifications. The platform maintains forks of Flutter and +Dart. When you install Shorebird, it provides custom Flutter and Dart copies +that produce +[Shorebird-enabled binaries](https://docs.shorebird.dev/code-push/). + +Apple's developer agreement requires interpreted code for OTA updates, +prohibiting JIT compilation. To provide updates, Shorebird built a _custom Dart +interpreter_. To maintain Flutter’s high-performance, while still providing +interpreter-updates, Shorebird also introduced a novel linker phase, which +allows the vast majority of your app to still run out of the AOT compiled +binary, rather than interpreting after update. This linker analyzes two Dart +programs (release and patch), finds maximal similarity, and determines +per-function whether to use the original binary or interpreter. Typically, 98%+ +of patched code runs from the original binary at full speed. + +The +[workflow](https://docs.shorebird.dev/code-push/guides/development-workflow/) +integrates seamlessly with existing development, with no required changes to +your application: ```bash shorebird release android # Build and register release @@ -126,15 +304,24 @@ shorebird release android # Build and register release shorebird patch android # Create and deploy patch ``` -[Patches](https://docs.shorebird.dev/code-push/patch/) use **binary diffing**. On Android, patches are typically a few kilobytes. iOS patches tend to be hundreds of kilobytes. Users receive patches on their next app restart, with automatic [rollback protection](https://docs.shorebird.dev/code-push/rollback/) if a patch fails to launch. +[Patches](https://docs.shorebird.dev/code-push/patch/) use **binary diffing**. +On Android, patches are typically a few kilobytes. iOS patches tend to be +hundreds of kilobytes. Users receive patches on their next app restart, with +automatic [rollback protection](https://docs.shorebird.dev/code-push/rollback/) +if a patch fails to launch. -Critically, Shorebird helps you maintain app store compliance: patches only modify Dart code (not native code, Flutter engine, or assets). +Critically, Shorebird helps you maintain app store compliance: patches only +modify Dart code (not native code, Flutter engine, or assets). ## Current best practices for Flutter development in 2026 -**Flutter 3.38.5** (December 2025) is the current stable release, bundled with **Dart 3.10.4**. Key requirements now include Java 17 minimum for Android, iOS 13+ minimum, and Android 16KB page size support for Google Play compliance. Impeller is fully default across iOS and Android API 29+. +**Flutter 3.38.5** (December 2025) is the current stable release, bundled with +**Dart 3.10.4**. Key requirements now include Java 17 minimum for Android, iOS +13+ minimum, and Android 16KB page size support for Google Play compliance. +Impeller is fully default across iOS and Android API 29+. -For static analysis, configure `analysis_options.yaml` to include `package:flutter_lints/flutter.yaml` and enable strict mode: +For static analysis, configure `analysis_options.yaml` to include +`package:flutter_lints/flutter.yaml` and enable strict mode: ```yaml analyzer: @@ -143,30 +330,70 @@ analyzer: strict-inference: true ``` -We recommend running `flutter analyze` before commits and `dart fix --apply` to automatically resolve deprecated API usage. For deeper analysis, [DCM (Dart Code Metrics)](https://dcm.dev/) provides complexity metrics and unused code detection. Also, use shorebird for testing and deploying patches quickly to your released app. - -State management in 2025-2026 favors *[Riverpod 3](https://riverpod.dev/)* for new projects (compile-time safety, modular architecture), *[Bloc](https://bloclibrary.dev/)* for enterprise applications requiring strict separation of concerns, and *[Provider](https://pub.dev/packages/provider)* for simpler applications. [Flutter Signals](https://pub.dev/packages/signals) has emerged as an option for local reactive state. - -Adopt a feature-first project structure for medium-to-large applications, splitting code into `core/`, `features/`, and `services/` directories with each feature containing its own data, domain, and presentation layers. Use `const` constructors liberally, keep widget trees shallow, and profile regularly with [DevTools](https://docs.flutter.dev/tools/devtools/overview). +We recommend running `flutter analyze` before commits and `dart fix --apply` to +automatically resolve deprecated API usage. For deeper analysis, +[DCM (Dart Code Metrics)](https://dcm.dev/) provides complexity metrics and +unused code detection. Also, use shorebird for testing and deploying patches +quickly to your released app. + +State management in 2025-2026 favors _[Riverpod 3](https://riverpod.dev/)_ for +new projects (compile-time safety, modular architecture), +_[Bloc](https://bloclibrary.dev/)_ for enterprise applications requiring strict +separation of concerns, and _[Provider](https://pub.dev/packages/provider)_ for +simpler applications. [Flutter Signals](https://pub.dev/packages/signals) has +emerged as an option for local reactive state. + +Adopt a feature-first project structure for medium-to-large applications, +splitting code into `core/`, `features/`, and `services/` directories with each +feature containing its own data, domain, and presentation layers. Use `const` +constructors liberally, keep widget trees shallow, and profile regularly with +[DevTools](https://docs.flutter.dev/tools/devtools/overview). ## Conclusion -Flutter's architecture delivers native performance through AOT compilation while maintaining development velocity through JIT-powered hot reload. Flutter’s custom Impeller rendering pipeline provides reliably smooth 60fps animations. The three-tree rendering architecture and platform embedder design enable true cross-platform code sharing without sacrificing native integration. - -The most significant evolution for production Flutter teams is [Shorebird's code push capability](https://docs.shorebird.dev/code-push/). By building a custom interpreter and novel per-function linker, [Shorebird solves the immutability problem](https://docs.shorebird.dev/code-push/system-architecture/) while maintaining app store compliance, enabling bug fixes in hours rather than days. Combined with [Shorebird CI's](https://docs.shorebird.dev/ci/) zero-config testing and modern static analysis practices, Flutter teams can ship confidently while retaining the ability to respond quickly to production issues. +Flutter's architecture delivers native performance through AOT compilation while +maintaining development velocity through JIT-powered hot reload. Flutter’s +custom Impeller rendering pipeline provides reliably smooth 60fps animations. +The three-tree rendering architecture and platform embedder design enable true +cross-platform code sharing without sacrificing native integration. + +The most significant evolution for production Flutter teams is +[Shorebird's code push capability](https://docs.shorebird.dev/code-push/). By +building a custom interpreter and novel per-function linker, +[Shorebird solves the immutability problem](https://docs.shorebird.dev/code-push/system-architecture/) +while maintaining app store compliance, enabling bug fixes in hours rather than +days. Combined with [Shorebird CI's](https://docs.shorebird.dev/ci/) zero-config +testing and modern static analysis practices, Flutter teams can ship confidently +while retaining the ability to respond quickly to production issues. ## Next Steps -Once you understand Flutter's architecture and update strategies, consider these resources to implement production-ready workflows: - -**Set up code push for your app**: Start with the [Shorebird Quick Start Guide](https://docs.shorebird.dev/getting-started/) to enable over-the-air updates in your Flutter application. The guide covers installation, initialization, and creating your first release in under 15 minutes. - -**Establish a deployment workflow**: Review the [Development Workflow Guide](https://docs.shorebird.dev/code-push/guides/development-workflow/) to understand best practices for managing releases and patches. Learn when to create patches versus new releases, how to test updates before deployment, and strategies for [staging patches](https://docs.shorebird.dev/code-push/guides/staging-patches/) to specific user groups. - -**Implement gradual rollouts**: Use [percentage-based rollouts](https://docs.shorebird.dev/code-push/guides/percentage-based-rollouts/) to validate that patched Dart code behaves correctly on real devices before it replaces the AOT-compiled logic for everyone. This lets you verify that a hotfix works across hardware, OS versions, and app states without committing every user to a full store update. - -**Secure your deployment pipeline**: Enable [patch signing](https://docs.shorebird.dev/code-push/guides/patch-signing/) so that only code produced by your release pipeline can be executed in place of the original AOT binary. This preserves the same trust model as app-store signing, even though code is now being delivered outside of the store. - - - - +Once you understand Flutter's architecture and update strategies, consider these +resources to implement production-ready workflows: + +**Set up code push for your app**: Start with the +[Shorebird Quick Start Guide](https://docs.shorebird.dev/getting-started/) to +enable over-the-air updates in your Flutter application. The guide covers +installation, initialization, and creating your first release in under 15 +minutes. + +**Establish a deployment workflow**: Review the +[Development Workflow Guide](https://docs.shorebird.dev/code-push/guides/development-workflow/) +to understand best practices for managing releases and patches. Learn when to +create patches versus new releases, how to test updates before deployment, and +strategies for +[staging patches](https://docs.shorebird.dev/code-push/guides/staging-patches/) +to specific user groups. + +**Implement gradual rollouts**: Use +[percentage-based rollouts](https://docs.shorebird.dev/code-push/guides/percentage-based-rollouts/) +to validate that patched Dart code behaves correctly on real devices before it +replaces the AOT-compiled logic for everyone. This lets you verify that a hotfix +works across hardware, OS versions, and app states without committing every user +to a full store update. + +**Secure your deployment pipeline**: Enable +[patch signing](https://docs.shorebird.dev/code-push/guides/patch-signing/) so +that only code produced by your release pipeline can be executed in place of the +original AOT binary. This preserves the same trust model as app-store signing, +even though code is now being delivered outside of the store. 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..c783bfc8 --- /dev/null +++ b/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.mdx @@ -0,0 +1,399 @@ +--- +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.ink/img/pako:eNplVNtO4zAQ_ZWRJaRdUdg20Fu0QtqWCK1UStUWKm2yD04yNF5SO7IdoFz-HWfSC2XzkvHczpnjSV5ZolJkPrvP1VOScW1hPogkwNERBNLqNRRKSGsq1214a1CDVWWSoQGTaET5t4pMwkmV5YL4iNKS7yq8QmNLjb80Sv4z1j8uNBqVPyJoTNRSihfUhlKHYcQ2yZDwPI958mCo4puSc140QMlZolWef48YVcxCg3Zmua0LMAUh4dKxp-ggDHJcOSIOKS5FnjrvZqbfsigtzE6SUj8iDQUnJxcwoSnIvCLyZA6JHJkzQiVzsGs2qHpDkXFDrRbhGJ9gIdIlWgNOHUdvC23KeKl5kcFiHtYZMHfyEV1XWb1QpnXugGAWO5ipk0smIhfcCkXOILzmNsngqcYiqeI12HWBcAwPuP6KGsx3knyCDQ5gFwQb7KfTXCaZkEtCvA2nWBqETRvqEIxp4r2LnFWbN-L35sr2rrGC1cY7_jSbTN3aqPgfJvWWTd2aFWl1s3XshkIg7kEipiSoSxqHQ9L3IGnLoL7TaY09rg__QRaiwFxIurlROOJr5RbjGAruFpkEPYZMWLBuL50GXwWdzsMDep9UHR2oOq3JjGrWW3tD5aa0bh2rw104Ec-YG7fp-w-LqFHJXSRZgy21SJlvdYkNtkK94tWRvVZpEbOZu4WI-c6Mq4VkkXx3NQWXf5Rabcu0KpcZ8-95btypJKEvBXdDrXZeTYMNVSkt8zttj5ow_5U9M791dnba9Xr9Zqvldb1uq8HWzPe803671-13Wt3zZu-s03tvsBcCbZ722ucdr913j9dte81Og2EqrNLX9W-H_j7vH01hcuE?type=png)](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. + +
+ +
+ +Understanding hot reload's technical implementation reveals its capabilities and +limitations. When you press `r`, the host machine scans for changed code, +recompiles affected libraries plus the main library, generates Dart kernel files +(an intermediate representation), and sends them to the device's Dart VM. The VM +reloads libraries, and the Flutter framework triggers a complete rebuild and +repaint of existing widgets. + +The +[Dart VM wiki](https://github.com/dart-lang/sdk/blob/main/runtime/docs/hot-reload.md) +describes this as _pervasive late-binding_: the program behaves as if method +lookup happens at every call site. However, field values are preserved; changing +an initializer doesn't affect already-initialized variables. Closures capture +their function at creation time and won't pick up changes. + +There are some changes that Hot Reload cannot handle, and a Hot Restart will be +required: + +- Enum-to-class conversions or generic type modifications +- Changes to `main()` or `initState()` +- Static field initializer changes +- Non-Dart-code changes (e.g., native code) (always require a full app restart) + +## Testing spans three tiers with distinct tradeoffs + +Another question that comes up sometimes is how one should think about testing +one’s Flutter app. After some trial and error, the Flutter framework itself +landed on a three-tiered testing pyramid principle: many fast unit tests, fewer +widget tests, and minimal integration tests. Golden tests are also often thrown +in there too, and should be used with caution. + +**Unit tests** use the [test](https://pub.dev/packages/test) package to verify +isolated functions and classes. External dependencies should be mocked using +packages like [Mockito](https://pub.dev/packages/mockito). These execute in +milliseconds and catch logic errors early. I often find it particularly helpful +to separate my app into “ui” (Flutter) code and “logic” pure Dart code, and +extensively test the logic with unit tests. + +**Widget tests** use +[flutter_test](https://api.flutter.dev/flutter/flutter_test/) and the +`WidgetTester` class to render widgets without a physical device. Key methods +include `pumpWidget()` to build the widget tree, `pump()` to advance by one +frame after state changes, and `pumpAndSettle()` to wait for all animations to +complete. The `find` API locates widgets by text, type, key, or icon, while +matchers like `findsOneWidget` and `findsNothing` verify results. These are very +useful for testing complex widgets quickly. They’re essentially Flutter-level +unit tests. + +**Golden Tests** are a controversial, but common, testing mechanism where the +exact pixels from your app are recorded and checked against past recordings. +It’s very effective at catching unintentional visual bugs before they reach +users, but they can be extremely painful to maintain across multiple platforms +and configurations. There are a variety of 3p providers who can help make this +easier, including WidgetBook.com. + +**Integration tests** use the +[integration_test](https://docs.flutter.dev/testing/integration-tests) package +(which replaced `flutter_driver`) to test complete app flows on real devices. +Tests run through `IntegrationTestWidgetsFlutterBinding` and can execute on +[Firebase Test Lab](https://firebase.google.com/docs/test-lab) for device farm +testing. Patrol is an alternative 3p approach as well. In general, integration +tests are the most expensive to write, maintain, and run, and should be used +sparingly. + +## AOT compilation creates the immutability problem + +As mentioned above, one of the limitations in stock Flutter is that once you’ve +built a binary for distribution, you are left at the whims of each platform. Not +anymore. This is a problem Shorebird.dev has solved for you. + +The build process using `flutter build apk` or `flutter build ipa` invokes AOT +compilation, transforming Dart source into native machine code. Tree shaking +removes unreachable code, +[R8](https://developer.android.com/studio/build/shrink-code) shrinks Java/Kotlin +code on Android, and the final binary is signed for distribution. Build times +typically range from **3-8 minutes for Android** and **10-25 minutes for iOS** +(with many common dependencies, including Firebase, often adding significant +overhead). + +Unlike React Native, where JavaScript bundles can be updated at runtime through +services like [Microsoft's CodePush](https://microsoft.github.io/code-push/), +Flutter's compiled Dart code is static machine code with no runtime interpreter +in release builds. Once published, fixing bugs requires a full app store +submission, typically taking days. + +That immutability shows up everywhere in day-to-day delivery. Every fix, no +matter how small, has to flow back through the full build and release pipeline. +New binaries must be produced, signed, and submitted for each platform before +users see the change. What makes this painful is not just store review time, but +the fact that there is no smaller unit of deployment. You cannot ship a patch, a +hotfix, or a targeted change. You can only ship a whole new app. + +## Shorebird enables over-the-air Dart code updates + +This release pain is one of the reasons we started Shorebird. +[Shorebird](https://shorebird.dev/), solves the distribution problem through +sophisticated engine modifications. The platform maintains forks of Flutter and +Dart. When you install Shorebird, it provides custom Flutter and Dart copies +that produce +[Shorebird-enabled binaries](https://docs.shorebird.dev/code-push/). + +Apple's developer agreement requires interpreted code for OTA updates, +prohibiting JIT compilation. To provide updates, Shorebird built a _custom Dart +interpreter_. To maintain Flutter’s high-performance, while still providing +interpreter-updates, Shorebird also introduced a novel linker phase, which +allows the vast majority of your app to still run out of the AOT compiled +binary, rather than interpreting after update. This linker analyzes two Dart +programs (release and patch), finds maximal similarity, and determines +per-function whether to use the original binary or interpreter. Typically, 98%+ +of patched code runs from the original binary at full speed. + +The +[workflow](https://docs.shorebird.dev/code-push/guides/development-workflow/) +integrates seamlessly with existing development, with no required changes to +your application: + +```bash +shorebird release android # Build and register release +# Submit to app stores... +# Fix bug in Dart code... +shorebird patch android # Create and deploy patch +``` + +[Patches](https://docs.shorebird.dev/code-push/patch/) use **binary diffing**. +On Android, patches are typically a few kilobytes. iOS patches tend to be +hundreds of kilobytes. Users receive patches on their next app restart, with +automatic [rollback protection](https://docs.shorebird.dev/code-push/rollback/) +if a patch fails to launch. + +Critically, Shorebird helps you maintain app store compliance: patches only +modify Dart code (not native code, Flutter engine, or assets). + +## Current best practices for Flutter development in 2026 + +**Flutter 3.38.5** (December 2025) is the current stable release, bundled with +**Dart 3.10.4**. Key requirements now include Java 17 minimum for Android, iOS +13+ minimum, and Android 16KB page size support for Google Play compliance. +Impeller is fully default across iOS and Android API 29+. + +For static analysis, configure `analysis_options.yaml` to include +`package:flutter_lints/flutter.yaml` and enable strict mode: + +```yaml +analyzer: + language: + strict-casts: true + strict-inference: true +``` + +We recommend running `flutter analyze` before commits and `dart fix --apply` to +automatically resolve deprecated API usage. For deeper analysis, +[DCM (Dart Code Metrics)](https://dcm.dev/) provides complexity metrics and +unused code detection. Also, use shorebird for testing and deploying patches +quickly to your released app. + +State management in 2025-2026 favors _[Riverpod 3](https://riverpod.dev/)_ for +new projects (compile-time safety, modular architecture), +_[Bloc](https://bloclibrary.dev/)_ for enterprise applications requiring strict +separation of concerns, and _[Provider](https://pub.dev/packages/provider)_ for +simpler applications. [Flutter Signals](https://pub.dev/packages/signals) has +emerged as an option for local reactive state. + +Adopt a feature-first project structure for medium-to-large applications, +splitting code into `core/`, `features/`, and `services/` directories with each +feature containing its own data, domain, and presentation layers. Use `const` +constructors liberally, keep widget trees shallow, and profile regularly with +[DevTools](https://docs.flutter.dev/tools/devtools/overview). + +## Conclusion + +Flutter's architecture delivers native performance through AOT compilation while +maintaining development velocity through JIT-powered hot reload. Flutter’s +custom Impeller rendering pipeline provides reliably smooth 60fps animations. +The three-tree rendering architecture and platform embedder design enable true +cross-platform code sharing without sacrificing native integration. + +The most significant evolution for production Flutter teams is +[Shorebird's code push capability](https://docs.shorebird.dev/code-push/). By +building a custom interpreter and novel per-function linker, +[Shorebird solves the immutability problem](https://docs.shorebird.dev/code-push/system-architecture/) +while maintaining app store compliance, enabling bug fixes in hours rather than +days. Combined with [Shorebird CI's](https://docs.shorebird.dev/ci/) zero-config +testing and modern static analysis practices, Flutter teams can ship confidently +while retaining the ability to respond quickly to production issues. + +## Next Steps + +Once you understand Flutter's architecture and update strategies, consider these +resources to implement production-ready workflows: + +**Set up code push for your app**: Start with the +[Shorebird Quick Start Guide](https://docs.shorebird.dev/getting-started/) to +enable over-the-air updates in your Flutter application. The guide covers +installation, initialization, and creating your first release in under 15 +minutes. + +**Establish a deployment workflow**: Review the +[Development Workflow Guide](https://docs.shorebird.dev/code-push/guides/development-workflow/) +to understand best practices for managing releases and patches. Learn when to +create patches versus new releases, how to test updates before deployment, and +strategies for +[staging patches](https://docs.shorebird.dev/code-push/guides/staging-patches/) +to specific user groups. + +**Implement gradual rollouts**: Use +[percentage-based rollouts](https://docs.shorebird.dev/code-push/guides/percentage-based-rollouts/) +to validate that patched Dart code behaves correctly on real devices before it +replaces the AOT-compiled logic for everyone. This lets you verify that a hotfix +works across hardware, OS versions, and app states without committing every user +to a full store update. + +**Secure your deployment pipeline**: Enable +[patch signing](https://docs.shorebird.dev/code-push/guides/patch-signing/) so +that only code produced by your release pipeline can be executed in place of the +original AOT binary. This preserves the same trust model as app-store signing, +even though code is now being delivered outside of the store. From df1d4e47c659eae0c61bd8fc83e3b6fca16e8ccb Mon Sep 17 00:00:00 2001 From: Tom Arra Date: Tue, 20 Jan 2026 11:29:29 -0600 Subject: [PATCH 3/6] remove md file --- .../Flutter-concepts/Flutter-SDK-deep-dive.md | 399 ------------------ 1 file changed, 399 deletions(-) delete mode 100644 src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md diff --git a/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md b/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md deleted file mode 100644 index c783bfc8..00000000 --- a/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.md +++ /dev/null @@ -1,399 +0,0 @@ ---- -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.ink/img/pako:eNplVNtO4zAQ_ZWRJaRdUdg20Fu0QtqWCK1UStUWKm2yD04yNF5SO7IdoFz-HWfSC2XzkvHczpnjSV5ZolJkPrvP1VOScW1hPogkwNERBNLqNRRKSGsq1214a1CDVWWSoQGTaET5t4pMwkmV5YL4iNKS7yq8QmNLjb80Sv4z1j8uNBqVPyJoTNRSihfUhlKHYcQ2yZDwPI958mCo4puSc140QMlZolWef48YVcxCg3Zmua0LMAUh4dKxp-ggDHJcOSIOKS5FnjrvZqbfsigtzE6SUj8iDQUnJxcwoSnIvCLyZA6JHJkzQiVzsGs2qHpDkXFDrRbhGJ9gIdIlWgNOHUdvC23KeKl5kcFiHtYZMHfyEV1XWb1QpnXugGAWO5ipk0smIhfcCkXOILzmNsngqcYiqeI12HWBcAwPuP6KGsx3knyCDQ5gFwQb7KfTXCaZkEtCvA2nWBqETRvqEIxp4r2LnFWbN-L35sr2rrGC1cY7_jSbTN3aqPgfJvWWTd2aFWl1s3XshkIg7kEipiSoSxqHQ9L3IGnLoL7TaY09rg__QRaiwFxIurlROOJr5RbjGAruFpkEPYZMWLBuL50GXwWdzsMDep9UHR2oOq3JjGrWW3tD5aa0bh2rw104Ec-YG7fp-w-LqFHJXSRZgy21SJlvdYkNtkK94tWRvVZpEbOZu4WI-c6Mq4VkkXx3NQWXf5Rabcu0KpcZ8-95btypJKEvBXdDrXZeTYMNVSkt8zttj5ow_5U9M791dnba9Xr9Zqvldb1uq8HWzPe803671-13Wt3zZu-s03tvsBcCbZ722ucdr913j9dte81Og2EqrNLX9W-H_j7vH01hcuE?type=png)](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. - -
- -
- -Understanding hot reload's technical implementation reveals its capabilities and -limitations. When you press `r`, the host machine scans for changed code, -recompiles affected libraries plus the main library, generates Dart kernel files -(an intermediate representation), and sends them to the device's Dart VM. The VM -reloads libraries, and the Flutter framework triggers a complete rebuild and -repaint of existing widgets. - -The -[Dart VM wiki](https://github.com/dart-lang/sdk/blob/main/runtime/docs/hot-reload.md) -describes this as _pervasive late-binding_: the program behaves as if method -lookup happens at every call site. However, field values are preserved; changing -an initializer doesn't affect already-initialized variables. Closures capture -their function at creation time and won't pick up changes. - -There are some changes that Hot Reload cannot handle, and a Hot Restart will be -required: - -- Enum-to-class conversions or generic type modifications -- Changes to `main()` or `initState()` -- Static field initializer changes -- Non-Dart-code changes (e.g., native code) (always require a full app restart) - -## Testing spans three tiers with distinct tradeoffs - -Another question that comes up sometimes is how one should think about testing -one’s Flutter app. After some trial and error, the Flutter framework itself -landed on a three-tiered testing pyramid principle: many fast unit tests, fewer -widget tests, and minimal integration tests. Golden tests are also often thrown -in there too, and should be used with caution. - -**Unit tests** use the [test](https://pub.dev/packages/test) package to verify -isolated functions and classes. External dependencies should be mocked using -packages like [Mockito](https://pub.dev/packages/mockito). These execute in -milliseconds and catch logic errors early. I often find it particularly helpful -to separate my app into “ui” (Flutter) code and “logic” pure Dart code, and -extensively test the logic with unit tests. - -**Widget tests** use -[flutter_test](https://api.flutter.dev/flutter/flutter_test/) and the -`WidgetTester` class to render widgets without a physical device. Key methods -include `pumpWidget()` to build the widget tree, `pump()` to advance by one -frame after state changes, and `pumpAndSettle()` to wait for all animations to -complete. The `find` API locates widgets by text, type, key, or icon, while -matchers like `findsOneWidget` and `findsNothing` verify results. These are very -useful for testing complex widgets quickly. They’re essentially Flutter-level -unit tests. - -**Golden Tests** are a controversial, but common, testing mechanism where the -exact pixels from your app are recorded and checked against past recordings. -It’s very effective at catching unintentional visual bugs before they reach -users, but they can be extremely painful to maintain across multiple platforms -and configurations. There are a variety of 3p providers who can help make this -easier, including WidgetBook.com. - -**Integration tests** use the -[integration_test](https://docs.flutter.dev/testing/integration-tests) package -(which replaced `flutter_driver`) to test complete app flows on real devices. -Tests run through `IntegrationTestWidgetsFlutterBinding` and can execute on -[Firebase Test Lab](https://firebase.google.com/docs/test-lab) for device farm -testing. Patrol is an alternative 3p approach as well. In general, integration -tests are the most expensive to write, maintain, and run, and should be used -sparingly. - -## AOT compilation creates the immutability problem - -As mentioned above, one of the limitations in stock Flutter is that once you’ve -built a binary for distribution, you are left at the whims of each platform. Not -anymore. This is a problem Shorebird.dev has solved for you. - -The build process using `flutter build apk` or `flutter build ipa` invokes AOT -compilation, transforming Dart source into native machine code. Tree shaking -removes unreachable code, -[R8](https://developer.android.com/studio/build/shrink-code) shrinks Java/Kotlin -code on Android, and the final binary is signed for distribution. Build times -typically range from **3-8 minutes for Android** and **10-25 minutes for iOS** -(with many common dependencies, including Firebase, often adding significant -overhead). - -Unlike React Native, where JavaScript bundles can be updated at runtime through -services like [Microsoft's CodePush](https://microsoft.github.io/code-push/), -Flutter's compiled Dart code is static machine code with no runtime interpreter -in release builds. Once published, fixing bugs requires a full app store -submission, typically taking days. - -That immutability shows up everywhere in day-to-day delivery. Every fix, no -matter how small, has to flow back through the full build and release pipeline. -New binaries must be produced, signed, and submitted for each platform before -users see the change. What makes this painful is not just store review time, but -the fact that there is no smaller unit of deployment. You cannot ship a patch, a -hotfix, or a targeted change. You can only ship a whole new app. - -## Shorebird enables over-the-air Dart code updates - -This release pain is one of the reasons we started Shorebird. -[Shorebird](https://shorebird.dev/), solves the distribution problem through -sophisticated engine modifications. The platform maintains forks of Flutter and -Dart. When you install Shorebird, it provides custom Flutter and Dart copies -that produce -[Shorebird-enabled binaries](https://docs.shorebird.dev/code-push/). - -Apple's developer agreement requires interpreted code for OTA updates, -prohibiting JIT compilation. To provide updates, Shorebird built a _custom Dart -interpreter_. To maintain Flutter’s high-performance, while still providing -interpreter-updates, Shorebird also introduced a novel linker phase, which -allows the vast majority of your app to still run out of the AOT compiled -binary, rather than interpreting after update. This linker analyzes two Dart -programs (release and patch), finds maximal similarity, and determines -per-function whether to use the original binary or interpreter. Typically, 98%+ -of patched code runs from the original binary at full speed. - -The -[workflow](https://docs.shorebird.dev/code-push/guides/development-workflow/) -integrates seamlessly with existing development, with no required changes to -your application: - -```bash -shorebird release android # Build and register release -# Submit to app stores... -# Fix bug in Dart code... -shorebird patch android # Create and deploy patch -``` - -[Patches](https://docs.shorebird.dev/code-push/patch/) use **binary diffing**. -On Android, patches are typically a few kilobytes. iOS patches tend to be -hundreds of kilobytes. Users receive patches on their next app restart, with -automatic [rollback protection](https://docs.shorebird.dev/code-push/rollback/) -if a patch fails to launch. - -Critically, Shorebird helps you maintain app store compliance: patches only -modify Dart code (not native code, Flutter engine, or assets). - -## Current best practices for Flutter development in 2026 - -**Flutter 3.38.5** (December 2025) is the current stable release, bundled with -**Dart 3.10.4**. Key requirements now include Java 17 minimum for Android, iOS -13+ minimum, and Android 16KB page size support for Google Play compliance. -Impeller is fully default across iOS and Android API 29+. - -For static analysis, configure `analysis_options.yaml` to include -`package:flutter_lints/flutter.yaml` and enable strict mode: - -```yaml -analyzer: - language: - strict-casts: true - strict-inference: true -``` - -We recommend running `flutter analyze` before commits and `dart fix --apply` to -automatically resolve deprecated API usage. For deeper analysis, -[DCM (Dart Code Metrics)](https://dcm.dev/) provides complexity metrics and -unused code detection. Also, use shorebird for testing and deploying patches -quickly to your released app. - -State management in 2025-2026 favors _[Riverpod 3](https://riverpod.dev/)_ for -new projects (compile-time safety, modular architecture), -_[Bloc](https://bloclibrary.dev/)_ for enterprise applications requiring strict -separation of concerns, and _[Provider](https://pub.dev/packages/provider)_ for -simpler applications. [Flutter Signals](https://pub.dev/packages/signals) has -emerged as an option for local reactive state. - -Adopt a feature-first project structure for medium-to-large applications, -splitting code into `core/`, `features/`, and `services/` directories with each -feature containing its own data, domain, and presentation layers. Use `const` -constructors liberally, keep widget trees shallow, and profile regularly with -[DevTools](https://docs.flutter.dev/tools/devtools/overview). - -## Conclusion - -Flutter's architecture delivers native performance through AOT compilation while -maintaining development velocity through JIT-powered hot reload. Flutter’s -custom Impeller rendering pipeline provides reliably smooth 60fps animations. -The three-tree rendering architecture and platform embedder design enable true -cross-platform code sharing without sacrificing native integration. - -The most significant evolution for production Flutter teams is -[Shorebird's code push capability](https://docs.shorebird.dev/code-push/). By -building a custom interpreter and novel per-function linker, -[Shorebird solves the immutability problem](https://docs.shorebird.dev/code-push/system-architecture/) -while maintaining app store compliance, enabling bug fixes in hours rather than -days. Combined with [Shorebird CI's](https://docs.shorebird.dev/ci/) zero-config -testing and modern static analysis practices, Flutter teams can ship confidently -while retaining the ability to respond quickly to production issues. - -## Next Steps - -Once you understand Flutter's architecture and update strategies, consider these -resources to implement production-ready workflows: - -**Set up code push for your app**: Start with the -[Shorebird Quick Start Guide](https://docs.shorebird.dev/getting-started/) to -enable over-the-air updates in your Flutter application. The guide covers -installation, initialization, and creating your first release in under 15 -minutes. - -**Establish a deployment workflow**: Review the -[Development Workflow Guide](https://docs.shorebird.dev/code-push/guides/development-workflow/) -to understand best practices for managing releases and patches. Learn when to -create patches versus new releases, how to test updates before deployment, and -strategies for -[staging patches](https://docs.shorebird.dev/code-push/guides/staging-patches/) -to specific user groups. - -**Implement gradual rollouts**: Use -[percentage-based rollouts](https://docs.shorebird.dev/code-push/guides/percentage-based-rollouts/) -to validate that patched Dart code behaves correctly on real devices before it -replaces the AOT-compiled logic for everyone. This lets you verify that a hotfix -works across hardware, OS versions, and app states without committing every user -to a full store update. - -**Secure your deployment pipeline**: Enable -[patch signing](https://docs.shorebird.dev/code-push/guides/patch-signing/) so -that only code produced by your release pipeline can be executed in place of the -original AOT binary. This preserves the same trust model as app-store signing, -even though code is now being delivered outside of the store. From 2b5471574b092eb372906068fa34f0fd67f85cc5 Mon Sep 17 00:00:00 2001 From: Tom Arra Date: Tue, 20 Jan 2026 13:40:36 -0600 Subject: [PATCH 4/6] get the video embed looking better --- .../docs/Flutter-concepts/Flutter-SDK-deep-dive.mdx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.mdx b/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.mdx index c783bfc8..dfe19e34 100644 --- a/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.mdx +++ b/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.mdx @@ -178,8 +178,16 @@ 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. -
- +
+
Understanding hot reload's technical implementation reveals its capabilities and From aed2c8686f9f11cdc12a239c36ad7fba1e9fc1d6 Mon Sep 17 00:00:00 2001 From: Tom Arra Date: Tue, 20 Jan 2026 13:48:18 -0600 Subject: [PATCH 5/6] spelling fixes --- .cspell.yaml | 3 +++ 1 file changed, 3 insertions(+) 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 From 68f5cb31472ccf33f62599d52244ef77d8121df1 Mon Sep 17 00:00:00 2001 From: Tom Arra Date: Tue, 20 Jan 2026 13:50:42 -0600 Subject: [PATCH 6/6] update path --- .../flutter-sdk-deep-dive.mdx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/content/docs/{Flutter-concepts/Flutter-SDK-deep-dive.mdx => flutter-concepts/flutter-sdk-deep-dive.mdx} (100%) diff --git a/src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.mdx b/src/content/docs/flutter-concepts/flutter-sdk-deep-dive.mdx similarity index 100% rename from src/content/docs/Flutter-concepts/Flutter-SDK-deep-dive.mdx rename to src/content/docs/flutter-concepts/flutter-sdk-deep-dive.mdx