DEV Community

Cover image for # Your packages haven't migrated to `material_ui`. Can you?
marwaMejri
marwaMejri

Posted on

# Your packages haven't migrated to `material_ui`. Can you?

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 release list, with the app's green theme and serif font

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.
Enter fullscreen mode Exit fullscreen mode

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(),
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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(),
  ),
  // ...
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

and pass legacy.InputDecoration(...). It compiles, and flutter analyze
finds nothing. Then the settings screen opens:

A red error screen: No Material widget found

No Material widget found.
DropdownButton2<String> widgets require a Material widget ancestor within the closest LookupBoundary.
Enter fullscreen mode Exit fullscreen mode

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>(/* ... */),
)
Enter fullscreen mode Exit fullscreen mode

A new red screen:

A red error screen: No MaterialLocalizations found

No MaterialLocalizations found.
DropdownButton2<String> widgets require MaterialLocalizations to be provided by a Localizations widget ancestor.
Enter fullscreen mode Exit fullscreen mode

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!);
},
Enter fullscreen mode Exit fullscreen mode

The field is back, with the app's font and colors:

The settings screen working, with the menu open

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!);
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

This file compiled after dart fix. No error, no warning. Then I opened the screen:

The same screen before and after the migration: the Markdown text loses its font, the code block loses its color

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..."
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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)),
Enter fullscreen mode Exit fullscreen mode

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)'.
Enter fullscreen mode Exit fullscreen mode

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),
    ),
  ),
)
Enter fullscreen mode Exit fullscreen mode

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}'),
Enter fullscreen mode Exit fullscreen mode

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": []>
Enter fullscreen mode Exit fullscreen mode

go_router can tell you what happened. With debugLogDiagnostics: true on the GoRouter, the console prints:

[GoRouter] Using WidgetsApp configuration
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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):
Enter fullscreen mode Exit fullscreen mode

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 outdated shows the latest version, and its changelog says whether it moved to material_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.of count is a hint, not a proof. dropdown_button2 and flutter_markdown_plus read the theme, and both lost it. But go_router 17.5.0 reads it zero times and still broke, because it checks the type of your MaterialApp.
  • leak_tracker_flutter_testing comes with flutter_test and 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:

  1. 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.
  2. 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.
  3. 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 add brought material_ui into 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)