Repository navigation
Description
Summary
MaterialUiCompatibilityBridge resolves legacy dependencies that read Material state from the tree (Theme.of, MaterialLocalizations.of). It cannot resolve dependencies that expose an SDK Material type in their public API, because those values cross the boundary as arguments in a call signature rather than as inherited-widget lookups. No widget wrapper can intercept that direction.
This is offered as a documentation/design observation rather than a defect report. The bridge behaves exactly as designed. The issue is that the two coupling modes are indistinguishable to a developer until they compile, which makes migration planning harder than it needs to be.
Steps to reproduce
- Depend on a package that returns an SDK Material type from its public API. Minimal real example:
flutter_expandable_fab, whoseExpandableFab.locationis backed by_ExpandableFabLocation extends StandardFabLocation(SDK). - Migrate the app to
material_ui. - Pass that value to a
material_uiScaffold:
Scaffold(
floatingActionButton: someFab,
floatingActionButtonLocation: ExpandableFab.location,
)- Wrap in
MaterialUiCompatibilityBridgeat any level, app-wide viaMaterialApp.builderor around the subtree.
Expected results
Some documented path for a legacy package during the coexistence window, or explicit guidance that this case has none.
Actual results
lib/features/journey_detail/journey_detail_page.dart:131:55: Error: The argument type 'FloatingActionButtonLocation/*1*/' can't be assigned to the parameter type 'FloatingActionButtonLocation/*2*/?'.
- 'FloatingActionButtonLocation/*1*/' is from 'package:flutter/src/material/floating_action_button_location.dart'
- 'FloatingActionButtonLocation/*2*/' is from 'package:material_ui/src/floating_action_button_location.dart'
floatingActionButtonLocation: ExpandableFab.location,
^
The bridge has no effect on this error, and structurally cannot: it injects Theme and Localizations downward into the subtree, while this value travels upward as a call argument.
Why an adapter does not work either
The natural workaround is to implement the modern type and delegate to the legacy instance. It fails one level deeper:
class BridgedFabLocation extends modern.StandardFabLocation {
final legacy.FloatingActionButtonLocation legacyLocation = ExpandableFab.location;
@override
double getOffsetX(modern.ScaffoldPrelayoutGeometry geometry, double adjustment) {
return (legacyLocation as legacy.StandardFabLocation).getOffsetX(geometry, adjustment);
}
}error • The argument type 'ScaffoldPrelayoutGeometry (… material_ui-1.0.1/lib/src/scaffold.dart)'
can't be assigned to the parameter type 'ScaffoldPrelayoutGeometry (… flutter/lib/src/material/scaffold.dart)'
The override signature is fixed by material_ui and supplies a modern ScaffoldPrelayoutGeometry; the legacy method requires the SDK one. That object is constructed by the Scaffold during its layout pass, so there is no value to convert and no seam to inject a conversion into. Adapting one type relocates the incompatibility inward rather than resolving it.
Context: measured on a production app
Flutter 3.44.7 stable, material_ui 1.0.1, ~18 widget-rendering plugin dependencies. Full migration: 214 import sites across 212 files.
Baseline before migration: 0 errors. After migration: 3 errors (flutter build apk --debug agrees with flutter analyze).
| Site | Cause | Bridge resolves it |
|---|---|---|
MarkdownStyleSheet.fromTheme(Theme.of(context)) ×2 |
context lookup | yes |
Scaffold(floatingActionButtonLocation: ExpandableFab.location) |
API signature | no |
Wrapping the first two in MaterialUiCompatibilityBridge resolves them. The third is the only remaining error in the entire application.
The takeaway is that the bridge is effective — it reduced ~18 candidate blockers to one. But that one is unconditional: migration stays atomic while any API-signature-coupled package remains in the graph, regardless of bridge coverage.
Related
- [material_ui] No migration path for dynamic_color because it exposes the legacy flutter/material.dart ColorScheme #191051 —
dynamic_colorhit the same class of problem (it returns a legacyColorSchemethrough a callback). The thread closed on a downstream fix, and the bridge limitation itself was acknowledged but not tracked separately. This issue is intended to track that limitation, not to request changes to any third-party package. - material_ui TextTheme conflict after Flutter/package update - previously working [google_fonts] #191067 —
google_fontsTextTheme, same class, first-party. - ☂️ Move the material and cupertino packages outside of Flutter #101479 — decoupling umbrella.
Suggestion
Two things that would have saved the most time, in rough priority:
- State the limit in the
MaterialUiCompatibilityBridgedartdoc and the migration guide. Something as short as "the bridge covers packages that read theme/localizations from context; it cannot help packages that expose SDK Material types in their public API" would let teams classify their dependencies before starting. - Consider surfacing it mechanically. The signal is static — does this package expose, return, or subclass an SDK Material type in its public API? If
dart fix --code=migrate_design_widgetscould warn when it detects such a dependency, teams would learn the true blast radius up front instead of mid-migration. In our case a single check would have shown the entire cutover hinged on one FAB.
Happy to test any revision against this dependency set. If this is already understood and simply out of scope for 1.x, feel free to close.
Activity
Piinks commentedon Aug 20, 2026
Thanks for the detailed report!!
In cases like
flutter_expandable_fab, that package migrating to material_ui should resolve the issue. The bridge unfortunately can't cover every gap, but glad to hear it covered most cases!The doc suggestions are very good here.
Add design migration guide (flutter#13821)
Add design migration guide (#13821)
github-actions commentedon Sep 11, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.