Where we left off
In the previous article, I migrated a small app from the SDK's Material to the material_ui package and measured every step. The app had no third-party UI package, on purpose. The result: seventeen automatic fixes, one fix by hand, and the same ten tests green before and after. Plus three things dart fix misses.
Real apps are not like that. They use a font package, a router, a form widget, something to render Markdown. And for each of these packages, the same question comes back: has it migrated yet?
I said I would add real packages to that app. I built a second one instead, so that each package has a natural place in it, and nothing from the first app can hide a result. It uses four packages. Two of them have already published a migrated version. Two have not.
Why packages are the hard part
After the migration, your app contains two Materials: the frozen copy inside the SDK, and the material_ui package. The class names are the same. For Dart, the classes are not. The previous article showed what that looks like: the compiler refuses to assign a ColorScheme to a ColorScheme.
In your own code, dart fix moves every file to material_ui. It cannot touch your dependencies. Their code lives in the pub cache, and it keeps importing whatever its author chose.
Why authors don't just migrate
For a package author, migrating is a breaking change. The migration guide says it directly:
Treat this migration as a major version bump for your package.
The migrated version expects the new types. An app that has not migrated can still install it, but the package and the app no longer share the same Material.
So each author has to pick a moment, and every app that depends on the package lives with that choice.
Some have already picked. go_router 18.0.0, animations 3.0.0 and google_fonts 9.0.0 are all major versions, and the first line of each changelog says the same thing: the package now uses material_ui.
The bridge, again
For this period, the guide offers the compatibility bridge. The previous article tested it. In short, it helps a package that reads the theme from context, but not one that has Material types in its API. It copies only four properties to the old theme: platform, visualDensity, colorScheme and textTheme. And it is already marked @Deprecated, still in material_ui 1.7.0, the latest version as I write.
Three ways a package can get in your way
Loud. The package has SDK Material types in its public API. It returns a TextTheme, or takes an InputDecoration. Your migrated code passes the new types, and the compiler refuses. You see it right away.
Silent. The package uses Material only inside its own code. It reads the SDK's Theme, or looks for the SDK's MaterialApp. It compiles. But your migrated app no longer provides either, so the package falls back to its defaults. No error, no warning.
Reversed. The package has migrated, your app has not. Now the package looks for the new Material in your app, and does not find it. In one such report, about animations 3.0.0, the Flutter team answered that this is expected: "Major releases indicate non backwards compatible changes."
(flutter#192920).
Each package below falls into at least one of these cases.
The test app
A small app, so that nothing hides a result: Notely, a fictional note-taking app, and its release notes. Three screens, four packages, each one where a real app would use it.
The theme is far from the default on purpose: teal instead of purple, Merriweather instead of Roboto, and a pale yellow cardTheme that colors the Markdown code blocks. If a widget loses it, you see it.
| Screen | What it does | Package |
|---|---|---|
| Release list | opens a release | go_router |
| Release details | shows the notes, written in Markdown | flutter_markdown_plus |
| Settings | a form field to pick the update channel | dropdown_button2 |
| The whole app | the font of the theme | google_fonts |
An app from before the migration
I pinned the four packages to the versions an existing app would still have,with no ^, so that flutter pub get could not move them:
| Package | Pinned version | Latest version on 15 October | Migrated? |
|---|---|---|---|
google_fonts |
8.2.1 | 9.0.0 | yes |
go_router |
17.5.0 | 18.1.0 | yes |
dropdown_button2 |
3.1.0 | 3.1.0 | no |
flutter_markdown_plus |
1.0.12 | 1.0.12 | no |
Two of them have a migrated version, two have none. The latest versions come from flutter pub outdated, and the last column from each package's changelog and source code. Everything was measured with Flutter 3.47.7, Dart 3.13.5 and material_ui 1.7.0.
Five tests, written before the migration
| Test | What it checks |
|---|---|
| the app font | the title uses Merriweather |
| the Markdown font | the text of the notes uses Merriweather |
| the Markdown code block | the code block uses the yellow of the cardTheme
|
| the settings form | the field opens and switches to "Beta" |
| the page transition | 100 ms after a tap, both pages are on screen, so a transition is playing |
All five pass on the tag before-migration.
What dart fix sees
The same command as last time, on a dry run first:
7 proposed fixes in 7 files.
Five files in lib/, one test, and pubspec.yaml. Not one word about the four packages. The tool rewrites your imports and stops there: it does not look at your dependencies.
After applying it, flutter analyze finds two errors. The tests do not even load. Each package below explains part of what happened.
Loud, fixed by an upgrade: google_fonts
The theme gets its font in one line:
textTheme: GoogleFonts.merriweatherTextTheme(),
After dart fix, that line no longer compiles:
error - The argument type 'TextTheme' can't be assigned to the parameter type 'TextTheme?'. - lib\core\theme\app_theme.dart:14:18 - argument_type_not_assignable
TextTheme can't be assigned to TextTheme. The analyzer does not say why. The compiler does, in the output of flutter test:
- 'TextTheme/*1*/' is from 'package:flutter/src/material/text_theme.dart'
- 'TextTheme/*2*/' is from 'package:material_ui/src/text_theme.dart'
google_fonts 8.2.1 still imports package:flutter/material.dart, so its
merriweatherTextTheme() returns the SDK's TextTheme. Your migrated
ThemeData comes from material_ui and only accepts its own. Other developers hit the same error (flutter#191067).
The fix
google_fonts 9.0.0 uses material_ui. One command:
flutter pub add google_fonts:9.0.0
The error is gone, and the line of code did not change. Once the rest of the app compiled again, the font test passed.
What it costs
It is a major version, with its own changelog to read. Version 9.0.0 also
requires Flutter 3.47 or later, and brings material_ui into your dependency tree.
And there is no way around the migration. I added 9.0.0 to the app from before the migration: same line, same error. This time the TextTheme comes from material_ui, and the SDK's ThemeData refuses it. With google_fonts 9, you upgrade the package and migrate the app together, or you do neither.
Loud, with no fix yet: dropdown_button2
The settings screen has one form field:
DropdownButtonFormField2<String>(
decoration: const InputDecoration(
labelText: 'Update channel',
border: OutlineInputBorder(),
),
// ...
)
After dart fix, the second error:
error - The argument type 'InputDecoration' can't be assigned to the parameter type 'InputDecoration?'. -lib\features\settings\settings_page.dart:28:23
The same mistake as with google_fonts, in the other direction. This time your code passes the InputDecoration from material_ui, and the package expects the one from the SDK. And here, no upgrade helps: 3.1.0 is the latest version, and it still imports package:flutter/material.dart.
So I tried to work around it, one step at a time.
Try 1: give it the type it wants
Import the SDK's InputDecoration under a prefix, in this file only:
import 'package:flutter/material.dart'
as legacy
show InputDecoration, OutlineInputBorder;
and pass legacy.InputDecoration(...). It compiles, and flutter analyze
finds nothing. Then the settings screen opens:
No Material widget found.
DropdownButton2<String> widgets require a Material widget ancestor within the closest LookupBoundary.
The package also looks for a Material widget above it, and it wants the SDK's. The Scaffold of the migrated app provides the one from material_ui.
Try 2: give it a Material
Wrap the field in an SDK Material that draws nothing:
legacy.Material(
type: legacy.MaterialType.transparency,
child: DropdownButtonFormField2<String>(/* ... */),
)
A new red screen:
No MaterialLocalizations found.
DropdownButton2<String> widgets require MaterialLocalizations to be provided by a Localizations widget ancestor.
In its build method, DropdownButton2 runs two checks in a row: first a
Material, then MaterialLocalizations. Each fix only reveals the next one.
Try 3: add the bridge
This is the case the bridge was made for. It puts an SDK Theme and the SDK's MaterialLocalizations above the whole app:
builder: (BuildContext context, Widget? child) {
return MaterialUiCompatibilityBridge(child: child!);
},
The field is back, with the app's font and colors:
What it costs
Three workarounds for one widget, and all three must stay: the bridge does not fix the type, and it does not provide the Material. One of them is deprecated. And the bridge breaks flutter analyze in CI: its info is enough to make the command exit with code 1. You either silence it on that line, with the reason written next to it:
// Needed by dropdown_button2 3.1.0, which still uses the SDK Material.
// Remove when a migrated version is published.
// ignore: deprecated_member_use
return MaterialUiCompatibilityBridge(child: child!);
or run flutter analyze --no-fatal-infos.
The two red screens are debug checks. I did not test a release build.
Each try has its commit in the repo: try 1, try 2, try 3.
Silent: flutter_markdown_plus
The release details screen shows the notes with one widget:
Markdown(data: release.notes)
This file compiled after dart fix. No error, no warning. Then I opened the screen:
The app bar keeps the app's theme: it is my code, and it migrated. The Markdown below it does not. Merriweather became Roboto, and the yellow code block became the default pale purple.
flutter_markdown_plus 1.0.12 has no migrated version. To build its default style, it calls MarkdownStyleSheet.fromTheme(Theme.of(context)), and that Theme is the SDK's. The migrated app provides only the one from material_ui, so the package gets the default theme.
The test that lied
My test for the Markdown font passed after the migration, while the screen
showed Roboto. I printed the font of each piece of the paragraph:
TextSpan font=Merriweather_regular text=""
TextSpan font=null text=""
TextSpan font=Roboto text="Notely now saves you..."
The package draws its text with Text.rich, which wraps it in the default text style of the screen. That wrapper comes from the migrated Scaffold, so it was in Merriweather. My test read the wrapper. The text inside was in Roboto.
Once the test read the text itself, it failed as it should:
Expected: a string starting with 'Merriweather'
Actual: 'Roboto'
and it still passed on the app from before the migration. A style test can pass for the wrong reason. Check it against a screenshot at least once.
With the bridge: half fixed
The bridge copies textTheme to the SDK theme, so the text got Merriweather back. It does not copy cardTheme, so the code block stayed purple. One test green, one red, on the same widget.
The fix, and its trap
The obvious fix is to give the package a style sheet built from the app theme:
styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context)),
It does not compile. fromTheme expects the SDK's ThemeData:
error - The argument type 'ThemeData (where ThemeData is defined in
...\material_ui-1.7.0\lib\src\theme_data.dart)' can't be assigned to the
parameter type 'ThemeData (where ThemeData is defined in
...\flutter\lib\src\material\theme_data.dart)'.
The loud case again, this time inside the fix.
What works: the MarkdownStyleSheet constructor only takes types like
TextStyle, Decoration and EdgeInsets, and none of them comes from Material. And the package merges your style sheet into its default one. So you can give it only what is missing:
Markdown(
data: release.notes,
styleSheet: MarkdownStyleSheet(
code: TextStyle(
backgroundColor: Theme.of(context).cardTheme.color,
),
codeblockDecoration: BoxDecoration(
color: Theme.of(context).cardTheme.color,
borderRadius: BorderRadius.circular(2),
),
),
)
With the bridge and this style sheet, both Markdown tests pass.
What it costs
You have to know which theme properties the package reads, and set by hand each one the bridge does not copy. Nothing tells you which ones. Here, only the screenshot did.
Silent, then reversed: go_router
The list opens a release with one call:
onTap: () => context.go('/releases/${release.id}'),
In the migrated app, with 17.5.0
It compiled. It worked: tap a release, and its notes open. But the page
appears at once, with no transition. The test caught it: 100 ms after the tap, the list should still be on screen, under the new page coming in.
Expected: exactly one matching candidate
Actual: _TypeWidgetFinder:<Found 0 widgets with type "ReleasesPage": []>
go_router can tell you what happened. With debugLogDiagnostics: true on the GoRouter, the console prints:
[GoRouter] Using WidgetsApp configuration
To choose how to show a page, go_router checks what kind of app it runs in. In 17.5.0, the check is:
import 'package:flutter/material.dart';
bool isMaterialApp(BuildContext context) =>
context.findAncestorWidgetOfExactType<MaterialApp>() != null;
It looks for the SDK's MaterialApp. The migrated app uses the one from
material_ui: same name, another class. go_router finds no MaterialApp, no CupertinoApp, decides it runs in a plain WidgetsApp, and shows every page with no transition.
Nothing fails. The app only feels a little more abrupt. A bug report on the same mechanism, in the other direction, adds that the iOS slide and Hero animations go away too (flutter#192761).
The fix: 18.1.0
flutter pub add go_router:18.1.0
The test passes again, and the console now prints [GoRouter] Using MaterialApp configuration.
I compared the two versions. isMaterialApp is the same function, word for word. Only the import above it changed:
| Version | Import in lib/src/pages/material.dart
|
|---|---|
| 17.5.0 | import 'package:flutter/material.dart'; |
| 18.1.0 | import 'package:material_ui/material_ui.dart'; |
One line decides whether your pages animate.
Reversed: 18.1.0 in the app before the migration
Then the other way round. I went back to the app from before the migration and ran:
flutter pub add go_router
No version given. pub picked the latest one, and four packages came with it:
+ cupertino_ui 1.1.3
+ flutter_localizations 0.0.0 from sdk flutter
> go_router 18.1.0 (was 17.5.0)
+ intl 0.20.3
+ material_ui 1.7.0
It compiled. And the transition test failed, with the same message as above. Now go_router looks for the MaterialApp of material_ui, and the app still uses the SDK's.
So you can decide not to migrate, and one routine upgrade brings material_ui into your app anyway, with a bug and no warning.
The report above suggests a workaround: build each page yourself with a
pageBuilder and a MaterialPage from the same library as your MaterialApp. I did not test it.
How to check your own dependencies
dart fix does not look at your dependencies, so I wrote a small script that does. flutter pub get writes the location of every dependency in .dart_tool/package_config.json. The script opens each one in the pub cache and reads the imports in its lib/ folder. It is in the repo:tool/check_design_imports.dart. Copy it into your project and run it before you migrate:
dart run tool/check_design_imports.dart
On the app from before the migration:
Still on the SDK Material (5):
dropdown_button2 3.1.0: 2 files import the SDK Material, 8 reads of Theme.of in them
flutter_markdown_plus 1.0.12: 5 files import the SDK Material, 7 reads of Theme.of in them
go_router 17.5.0: 2 files import the SDK Material, 0 reads of Theme.of in them
google_fonts 8.2.1: 28 files import the SDK Material, 0 reads of Theme.of in them
leak_tracker_flutter_testing 3.0.10: 1 file imports the SDK Material, 0 reads of Theme.of in them
Already on material_ui (0):
How to read it:
-
Still on the SDK Material: each of these can break, loudly or silently. For each one, look for a migrated version:
flutter pub outdatedshows the latest version, and its changelog says whether it moved tomaterial_ui. If there is one, upgrade it in the same step as the migration. If there is none, plan a workaround, or another package, and check that screen by hand. -
Already on
material_ui: your app must migrate before it uses this version. Until then, stay on the previous major version. -
The
Theme.ofcount is a hint, not a proof.dropdown_button2andflutter_markdown_plusread the theme, and both lost it. Butgo_router17.5.0 reads it zero times and still broke, because it checks the type of yourMaterialApp. -
leak_tracker_flutter_testingcomes withflutter_testand only runs in your tests. You can leave it aside.
I ran the script on Windows only.
Before you migrate
Three habits made the difference here:
- Test how screens look, not only what they do. One font and one color per screen that uses a package. Write these tests before the migration, and make sure they pass on the old code.
- Use a theme far from the default. If your app looks like the default Material 3 theme, a widget that loses your theme looks fine.
-
Take a screenshot of each screen before, and compare after. The
screenshot caught what my Markdown test missed. And when you upgrade a package, give a version. An unversioned
flutter pub addbroughtmaterial_uiinto an app that had not migrated.
So, can you migrate yet?
| Package | Case | What happened | What fixed it |
|---|---|---|---|
google_fonts 8.2.1 |
loud | compile error on TextTheme
|
9.0.0, no code change |
dropdown_button2 3.1.0 |
loud, no migrated version | compile error, then two red screens | prefixed import, SDK Material, and the bridge |
flutter_markdown_plus 1.0.12 |
silent | default font and colors, no error | the bridge, and a style sheet written by hand |
go_router 17.5.0 |
silent | no page transitions, no error | 18.1.0, no code change |
go_router 18.1.0, app not migrated |
reversed | no page transitions, material_ui installed |
migrating the app, or staying on 17.x |
Yes, you can, but not with dart fix alone, and not without looking at your packages.
When a migrated version exists, it is the easy case: upgrade the package and migrate the app together. When there is none, each package is its own small project, built on a bridge that is already deprecated and that breaks flutter analyze in CI.
And waiting is not a safe place either. go_router and google_fonts have already moved. The longer an app waits, the more of its packages move without it. The Flutter 3.47 announcement schedules the old libraries for deprecation in the November stable release.
Everything here was measured between 15 and 19 October 2026, with Flutter
3.47.7. Packages keep publishing new versions, so check yours with the script before you trust this table. The repo has one commit per step and every command output: github.com/marwaMejri/material_ui_packages_migration.
If one of your packages broke in a way that is not here, tell me in the
comments. I would like to add it.





Top comments (0)