跪拜 Guibai
← All articles
Frontend · Android · Flutter

Flutter's UI Decoupling: The 184-File Split That Took Seven Years

By 张风捷特烈 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Flutter apps that target only iOS have been shipping with 182 unnecessary Material files, inflating binary size and complicating updates to either design system. The decoupling lets the Flutter team iterate Material 3 Expressive and iOS 26 Liquid Glass independently, and it removes a structural disadvantage against Compose Multiplatform's modular architecture.

Summary

A seven-year architectural decision to bundle Material Design and Cupertino directly into the Flutter framework is now being unwound. The 2026 release of `material_ui` and `cupertino_ui` as standalone packages marks the first concrete step in a three-phase plan to let apps pull in only the design system they need. Source-level analysis of the 0.0.2 packages shows the migration is still in an intermediate state: `material_ui` retains a runtime dependency on `cupertino_ui`, and `ThemeData` still carries a `cupertinoOverrideTheme` parameter alongside 29 deprecation annotations.

The core problem was a tree-shaking loop. A pure Cupertino app's import of `cupertino.dart` would trigger a chain that forced the entire Material library—182 files, larger than the rendering, painting, foundation, and gestures layers combined—into the final binary. Two earlier decoupling attempts failed in CI during 2023 and 2024. The current approach re-exports `package:flutter/widgets.dart` from both new packages, meaning most apps can migrate by changing a single import statement. The localization system has been moved but not yet split, and a speculated `BasicThemeData` base class does not exist in the shipped code.

Takeaways
Material and Cupertino code accounted for 32% of the Flutter framework's total UI code—234 files—before the split.
The `material` layer alone (182 files) was larger than the rendering, painting, foundation, and gestures layers combined.
A tree-shaking loop forced all Material code into every app that imported Cupertino, because `ThemeData` held a runtime reference to `NoDefaultCupertinoThemeData`.
Two prior decoupling attempts in 2023 and 2024 failed when hundreds of tests broke in CI and were rolled back.
The new `material_ui` 0.0.2 package still has a hard runtime dependency on `cupertino_ui`, proving the split is incomplete.
Both new packages re-export `package:flutter/widgets.dart`, so most apps can migrate by changing one import line.
`ThemeData` retains its `cupertinoOverrideTheme` parameter and `ThemeData.cupertino()` factory method, with 29 `@Deprecated` annotations signaling future cleanup.
The speculated `BasicThemeData` base class does not exist in the 0.0.2 source; the theme hierarchy remains two separate classes inheriting from SDK types.
Flutter 3.44 (May 2026) introduced package renaming and conditional dependency support that these new packages require.
Localization files were moved into the new packages but have not been split or refactored yet.
Conclusions

The asymmetry of the dependency—Material needs Cupertino at runtime but not vice versa—reveals that the coupling was never mutual; it was Material's design that absorbed iOS-specific concerns into its own API surface.

Shipping 29 deprecations in a 0.0.2 release signals that the Flutter team is using the package boundary as a forcing function to clean up a decade of accumulated API surface, not just to move files.

The re-export of `widgets.dart` is a clever compatibility shim, but it also means the new packages are not truly independent; they still lean on the framework's widget layer as a shared foundation.

External pressure from Compose Multiplatform and simultaneous major updates to both Material and iOS design languages created a window where the cost of inaction finally exceeded the cost of the migration.

The TODO comment referencing a Flutter team engineer by name in a public package file is a small but telling sign that this is still an internal migration in progress, not a finished product.

Concepts & terms
Tree-shaking
Dart's compiler optimization that removes unused code from the final binary by tracing reachable code paths from the `main()` entry point. It fails when a static reference creates a closed loop that forces all code in the loop to be retained.
@docImport
A Dart annotation used in documentation comments to create cross-package links for the dartdoc tool. Unlike a regular `import`, it has no effect on compilation or tree-shaking and does not create a runtime dependency.
InheritedWidget
Flutter's mechanism for efficiently propagating data down the widget tree. The theme system uses it so that `Theme.of(context)` can retrieve the nearest `ThemeData` without passing it explicitly through every widget constructor.
Golden tests
Pixel-level screenshot tests that compare rendered widget output against a known-good reference image. They were a major blocker in earlier decoupling attempts because the testing infrastructure did not support cross-package golden file comparisons.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗