Migrate Flutter Material and Cupertino imports to the material_ui and cupertino_ui packages
The full migration off package:flutter/material.dart and package:flutter/cupertino.dart onto material_ui 1.1.1 and cupertino_ui 1.0.2: what dart fix --code=migrate_design_widgets rewrites, why third-party widgets start throwing ancestor-lookup errors, what MaterialUiCompatibilityBridge actually fixes, and how the flutter_localizations dependency changes.
For an app whose only Material surface is its own code, this is a one-command, one-afternoon migration: flutter pub add material_ui, then dart fix --apply --code=migrate_design_widgets, then run the tests. The widget APIs are an identical copy of what was in the SDK, so nothing renders differently and no golden should move. What costs real time is the dependency graph. Every package that still imports package:flutter/material.dart drags a second, type-incompatible copy of Theme, Material, and MaterialLocalizations into your program, and its widgets will throw ancestor-lookup failures inside your migrated tree until you wrap the app in MaterialUiCompatibilityBridge. This guide targets the current stable channel, Flutter 3.47.2 with Dart 3.13.2, plus material_ui 1.1.1 and cupertino_ui 1.0.2.
The clock matters here. The in-SDK libraries are already frozen, and formal deprecation is scheduled for the November 2026 stable release.
Why this is not an optional cleanup
- The in-SDK copies receive no fixes. Flutter closed the Material and Cupertino directories in
flutter/flutterto all contributions on April 7, 2026. Every bug fix since then has landed influtter/packagesinstead.material_ui1.1.1 already carries fixes the SDK copy will never get, including theSearchAnchorrace where a stale async suggestion set replaced a newer one, andSlidervalue indicator labels being clipped instead of ellipsized at the screen edge. - Design updates stop waiting on the SDK train. Material and Cupertino used to ship on Flutter’s quarterly cadence, so a token tweak or a new
MenuAnchorargument waited for the next stable cut. Pinningmaterial_ui: ^1.1.1decouples that: 1.1.0 and 1.1.1 both landed between the 3.47 stable and today. - You can finally drop a design system you never used. Once the SDK copies are deleted, a Cupertino-only app stops carrying Material’s theming, typography, and icon metadata through tree-shaking, and vice versa.
- Localizations move with the widgets. The Material and Cupertino translated strings and delegates now live inside the packages, which is why
flutter_localizationsstops being something you list yourself. - If you publish a package, you are a blocker. One un-migrated leaf package forces the compatibility bridge on everyone downstream.
What breaks
| Area | Change | Severity |
|---|---|---|
| Imports | package:flutter/material.dart becomes package:material_ui/material_ui.dart; package:flutter/cupertino.dart becomes package:cupertino_ui/cupertino_ui.dart | high, fully automatable |
| Type identity | The SDK Material and the material_ui Material are different runtime types, so ancestor lookups do not cross the boundary | high, needs the bridge |
| Localization delegates | GlobalMaterialLocalizations and GlobalCupertinoLocalizations come from the packages, not from flutter_localizations | medium |
pubspec.yaml | Two new direct dependencies; flutter_localizations is no longer a direct dependency you need | medium |
| Generated code | Anything emitting package:flutter/material.dart into a .g.dart or .freezed.dart file needs a regenerate after the source pass | medium |
| Published packages | Migrating your own package is a breaking change for consumers, so it needs a major version bump | medium |
| Widget APIs | None. Constructors, parameters, and rendering are unchanged | none |
That last row is the whole reason this migration is tractable. material_ui 1.0.0 is a copy of the bundled library as of the April 2026 freeze, not a redesign.
Pre-flight checklist
- Flutter 3.44 or newer.
material_uiraised its floor to Flutter 3.44 / Dart 3.12 when the code moved out offlutter/flutter, and 3.47.2 is the current stable. Check withflutter --version. - A clean
flutter analyzebefore you start. You want the post-migration run to be comparable. - A branch.
dart fix --applyrewrites every matching file in one pass and there is no undo flag. - An inventory of dependencies that render Material or Cupertino widgets.
flutter pub deps --style=compactplusflutter pub outdatedgives you the list; anything last published before August 2026 has not migrated. - If you have golden tests, run them first and commit the baseline. They should not change, and that is the assertion.
Migration steps
-
Add the packages before you touch a single import. The
dart fixrule rewrites import strings; it does not editpubspec.yaml. Run it in the wrong order and you get a file full of unresolvable imports.# Flutter 3.47.2, Dart 3.13.2 flutter pub add material_ui flutter pub add cupertino_uiThat resolves to
material_ui: ^1.1.1andcupertino_ui: ^1.0.2today. If your app is Material-only you still getcupertino_uitransitively, becausematerial_uihas depended oncupertino_ui: ^1.0.0since its 1.0.1 release, but list it explicitly if you import it directly. Verify withflutter pub deps --style=compact | grep -E 'material_ui|cupertino_ui'and confirm both resolve. -
Rewrite the imports with the shipped fix. Both packages register the same analyzer fix, so one command handles Material and Cupertino together.
dart fix --dry-run --code=migrate_design_widgets # review first dart fix --apply --code=migrate_design_widgetsThe result is a one-line diff per file:
// Before: Flutter 3.43 and earlier import 'package:flutter/material.dart'; // After: material_ui 1.1.1 import 'package:material_ui/material_ui.dart';Nothing below the import line changes.
MaterialApp,Scaffold,ThemeData,Colors,showDialog, and every other name is exported under the same identifier. Verify withgrep -rn "package:flutter/material.dart\|package:flutter/cupertino.dart" lib testreturning nothing, thenflutter analyze. -
Point the localization delegates at the packages. The delegates and the translated strings moved into
material_uiandcupertino_ui, and the packages expose an aggregate getter that saves you listing three delegates by hand.// Before: flutter_localizations, Flutter 3.43 import 'package:flutter_localizations/flutter_localizations.dart'; localizationsDelegates: const <LocalizationsDelegate<Object>>[ GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ],// After: material_ui 1.1.1 import 'package:material_ui/material_ui.dart'; localizationsDelegates: GlobalMaterialLocalizations.delegates,GlobalMaterialLocalizations.delegatesalready includes the Cupertino and Widgets delegates. If you also rungen-l10n, your generatedAppLocalizations.delegateis unaffected and gets appended to that list as before. You can now dropflutter_localizationsfrom your owndependencies, though it will stay inpubspec.lock:cupertino_ui1.0.2 still depends on it, alongsidecollection: ^1.19.1andintl: ^0.20.2. Verify by launching with a non-English locale and checking a built-in string, for example long-pressing aTextFieldand confirming the paste affordance is translated. -
Bridge the dependencies that have not migrated. This is the step people skip and then debug for an hour. Wrap at the app level with
MaterialApp.builder:// material_ui 1.1.1 MaterialApp( theme: ThemeData(useMaterial3: true), builder: (BuildContext context, Widget? child) { return MaterialUiCompatibilityBridge(child: child!); }, home: const HomeScreen(), )The Cupertino side is symmetric:
// cupertino_ui 1.0.2 CupertinoApp( builder: (BuildContext context, Widget? child) { return CupertinoUiCompatibilityBridge(child: child!); }, home: const HomeScreen(), )You can also wrap a narrower subtree if only one screen embeds legacy widgets, which keeps the extra inherited widgets out of the rest of the tree. Verify by navigating to every screen that hosts a third-party widget. The bridge is temporary scaffolding: delete it once
flutter pub outdatedshows nothing left on the old imports. -
Regenerate anything a code generator wrote.
dart fixsees your source, not the templates that produced it. Re-run the generator after step 2 so emitted files stop importing the SDK library:dart run build_runner build --delete-conflicting-outputsThen check the leftovers
dart fixcannot reach:exportbarrels that re-export Material for consumers, conditional imports that select a Material implementation per platform, and any generator template of your own with the import path hardcoded as a string. Verify with the samegrepfrom step 2, widened to the whole repo rather than justlibandtest. -
If you publish a package, bump the major version. Switching a published package to
material_uichanges what its consumers must have in their ownpubspec.yaml. Shipping that as a minor release breaks apps silently: their widget tree ends up mixing sources with no compile error to point at it. Bump to the next major, note the requiredmaterial_uiconstraint in the changelog, and keep the previous major on a maintenance branch if you support older Flutter versions. Verify withdart pub publish --dry-run.
Verification
flutter analyzereports the same count as your pre-migration baseline, with nouri_does_not_existand nodeprecated_member_useon an import line.grep -rn "package:flutter/material.dart\|package:flutter/cupertino.dart" .finds nothing outside.dart_toolandpubspec.lock.flutter testpasses, golden tests included and unchanged. A moved golden means two copies of the library are rendering in the same tree, not that Material changed.- The app runs on a device and every screen that embeds a third-party widget renders with your theme, not with defaults.
- A non-English locale still shows translated built-in strings after step 3.
flutter build apk --release --analyze-size(or the iOS equivalent) as a size baseline for later, once the SDK copies are deleted and tree-shaking can actually drop the design system you do not use.
Rollback
Fully reversible today. The changes are a pubspec.yaml diff, one import line per file, a delegates list, and an optional bridge widget, so git revert of the migration commit puts you back on the SDK libraries with no data or build artifact to unwind. Two caveats: there is no reverse dart fix, so a manual rollback means editing every import back by hand, which is why step 0 is a branch. And after the November 2026 stable, reverting parks you on formally deprecated APIs that will be deleted, so treat rollback as a way to unblock a release, not as a decision.
Gotchas
“Could not find an ancestor of type MaterialLocalizations” from code you did not write. This is the type-identity problem showing up at runtime. A widget compiled against the SDK library calls MaterialLocalizations.of(context), which walks the tree looking for the inherited widget of its MaterialLocalizations type. Your material_ui MaterialApp inserted a different type with the same name, the lookup misses, and the assert fires. Theme.of(context) fails the same way, with “Could not find an ancestor of type Theme”. The bridge in step 4 exists specifically to insert the legacy inherited widgets alongside the new ones so both lookups resolve. It is not a workaround for a missing Scaffold: if the error comes from your own migrated code, you have the ordinary problem described in no Material widget found in Flutter, and the bridge will not help.
Unresolvable import right after running the fix. You ran dart fix before flutter pub add. Add the package, then re-run dart fix --apply --code=migrate_design_widgets; the rule is idempotent.
Do not leave both imports in one file. package:flutter/material.dart and package:material_ui/material_ui.dart export the same identifiers, so any file with both gets ambiguous-import errors on Material, Theme, Colors, and friends. Prefixing one of them compiles but gives you two design systems in one file, which is worse than the error. Pick one per file.
The freeze date and the deprecation date are not the same thing. The code freeze announcement said the SDK libraries would be deprecated in the stable release after 3.44. That slipped: 3.47 shipped on August 12, 2026 without the deprecation, and the 3.47 release notes now put formal deprecation in the November stable. Frozen since April, deprecated in November, deleted later. Plan against November, not against whatever your analyzer is quiet about today.
Asset manifests can shift even though widgets do not. material_ui 1.1.0 exposed the ink_sparkle shader asset through its own pubspec.yaml and dropped the stretch_effect shader. If you assert on the asset manifest or strip unused assets in a build step, that is a real diff to review.
Migrate imports and Flutter versions in separate commits. If you jump SDK versions during the same pass, any visual regression has two candidate causes. Land the SDK upgrade, confirm the app is clean, then migrate imports.
Related
- The announcement this migration follows up on, including the SwiftPM default that landed in the same release, is in Flutter 3.44 splits Material and Cupertino out of the SDK.
- Structurally this is the same shape of wide mechanical pass as migrating a Flutter web app from dart:html to package:web, including the part where
dart fixhandles the easy 95% and the dependency graph handles you. - For a deprecation that
dart fixexplicitly cannot automate, compare replacing Radio.groupValue and onChanged with RadioGroup. - If you are also moving to the current stable in this cycle, read what Flutter 3.47 changed for desktop rendering before you attribute a visual regression to the package swap.
- Ancestor-lookup failures are a family, not a one-off. ScaffoldMessenger.of(context) does not contain a Scaffold is the same debugging method applied to a different inherited widget.
Sources
- material_ui on pub.dev, version 1.1.1, and its changelog
- cupertino_ui on pub.dev, version 1.0.2
- Flutter’s Material and Cupertino code freeze, the Flutter blog
- What’s new in Flutter 3.44, the Flutter blog
- What’s new in Flutter 3.47, the Flutter blog
- Design system decoupling tracking issue, flutter/flutter
- Flutter 3.47.0 release notes, docs.flutter.dev
Comments
Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.