Start Debugging

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

What breaks

AreaChangeSeverity
Importspackage:flutter/material.dart becomes package:material_ui/material_ui.dart; package:flutter/cupertino.dart becomes package:cupertino_ui/cupertino_ui.darthigh, fully automatable
Type identityThe SDK Material and the material_ui Material are different runtime types, so ancestor lookups do not cross the boundaryhigh, needs the bridge
Localization delegatesGlobalMaterialLocalizations and GlobalCupertinoLocalizations come from the packages, not from flutter_localizationsmedium
pubspec.yamlTwo new direct dependencies; flutter_localizations is no longer a direct dependency you needmedium
Generated codeAnything emitting package:flutter/material.dart into a .g.dart or .freezed.dart file needs a regenerate after the source passmedium
Published packagesMigrating your own package is a breaking change for consumers, so it needs a major version bumpmedium
Widget APIsNone. Constructors, parameters, and rendering are unchangednone

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

Migration steps

  1. Add the packages before you touch a single import. The dart fix rule rewrites import strings; it does not edit pubspec.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_ui

    That resolves to material_ui: ^1.1.1 and cupertino_ui: ^1.0.2 today. If your app is Material-only you still get cupertino_ui transitively, because material_ui has depended on cupertino_ui: ^1.0.0 since its 1.0.1 release, but list it explicitly if you import it directly. Verify with flutter pub deps --style=compact | grep -E 'material_ui|cupertino_ui' and confirm both resolve.

  2. 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_widgets

    The 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 with grep -rn "package:flutter/material.dart\|package:flutter/cupertino.dart" lib test returning nothing, then flutter analyze.

  3. Point the localization delegates at the packages. The delegates and the translated strings moved into material_ui and cupertino_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.delegates already includes the Cupertino and Widgets delegates. If you also run gen-l10n, your generated AppLocalizations.delegate is unaffected and gets appended to that list as before. You can now drop flutter_localizations from your own dependencies, though it will stay in pubspec.lock: cupertino_ui 1.0.2 still depends on it, alongside collection: ^1.19.1 and intl: ^0.20.2. Verify by launching with a non-English locale and checking a built-in string, for example long-pressing a TextField and confirming the paste affordance is translated.

  4. 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 outdated shows nothing left on the old imports.

  5. Regenerate anything a code generator wrote. dart fix sees 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-outputs

    Then check the leftovers dart fix cannot reach: export barrels 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 same grep from step 2, widened to the whole repo rather than just lib and test.

  6. If you publish a package, bump the major version. Switching a published package to material_ui changes what its consumers must have in their own pubspec.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 required material_ui constraint in the changelog, and keep the previous major on a maintenance branch if you support older Flutter versions. Verify with dart pub publish --dry-run.

Verification

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.

Sources

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Back