How to Work with Material and Cupertino Decoupling in Flutter [Full Handbook] — Opportunihub
Course Remote

How to Work with Material and Cupertino Decoupling in Flutter [Full Handbook]

Atuoha Anthony · Remote

At a glance

Type
Course
Organisation
Atuoha Anthony
Location
Remote
Work mode
Remote
Deadline
Rolling / not stated
Posted
18 Aug 2026

About this course

<p>Earlier this year, I published <a href="https://www.freecodecamp.org/news/decoupling-material-and-cupertino-in-flutter/">Decoupling Material and Cupertino in Flutter</a>, which covered what was then a preview feature: Flutter's plan to separate the Material and Cupertino design libraries from the core SDK into standalone packages on pub.dev.</p> <p>At the time, the feature was in preview, the migration tooling was incomplete, and the ecosystem had not caught up. It was a directional piece, explaining where Flutter was heading and why.</p> <p>Flutter 3.47, released on August 12, 2026, changes that completely.</p> <p>The standalone <code>material_ui</code> and <code>cupertino_ui</code> packages have reached version 1.0. The migration tool is ready. The compatibility bridge is shipped. The deprecation clock on the old imports has officially started.</p> <p>This is no longer a preview or a direction. It's the present, and it affects every Flutter developer.</p> <p>This handbook is the complete practical guide to everything that has changed. It covers why the Flutter team made this architectural decision, what the new packages contain and how they differ from the old imports, how to migrate both automatically and manually, how to handle dependencies that haven't yet migrated, how localizations work now, what happens to your project's existing widgets, and the full deprecation timeline so you know exactly when the old way of doing things stops being supported.</p> <p>If you read the earlier article, this is the follow-up you have been waiting for. If you're coming to this fresh, everything you need is here.</p> <h2 id="heading-table-of-contents">Table of Contents</h2> <ul> <li><p><a href="#heading-prerequisites">Prerequisites</a></p> </li> <li><p><a href="#heading-what-changed-and-why-it-matters-the-full-picture">What Changed and Why It Matters: The Full Picture</a></p> <ul> <li><p><a href="#heading-why-the-flutter-team-did-this">Why the Flutter Team Did This</a></p> </li> <li><p><a href="#heading-the-impact-on-your-current-code">The Impact on Your Current Code</a></p> </li> </ul> </li> <li><p><a href="#heading-understanding-the-old-architecture">Understanding the Old Architecture</a></p> </li> <li><p><a href="#heading-the-new-architecture-standalone-packages">The New Architecture: Standalone Packages</a></p> </li> <li><p><a href="#heading-setting-up-adding-the-new-packages">Setting Up: Adding the New Packages</a></p> <ul> <li><p><a href="#heading-adding-materialui">Adding materialui</a></p> </li> <li><p><a href="#heading-adding-cupertinoui">Adding cupertinoui</a></p> </li> <li><p><a href="#heading-adding-both-at-once">Adding Both at Once</a></p> </li> </ul> </li> <li><p><a href="#heading-migrating-your-project-the-automated-path">Migrating Your Project: The Automated Path</a></p> <ul> <li><p><a href="#heading-step-1-run-the-migration-tool">Step 1: Run the Migration Tool</a></p> </li> <li><p><a href="#heading-step-2-handle-the-known-pubspecyaml-bug">Step 2: Handle the Known pubspec.yaml Bug</a></p> </li> <li><p><a href="#heading-step-3-verify-the-migration">Step 3: Verify the Migration</a></p> </li> <li><p><a href="#heading-what-the-tool-actually-changes">What the Tool Actually Changes</a></p> </li> </ul> </li> <li><p><a href="#heading-migrating-your-project-the-manual-path">Migrating Your Project: The Manual Path</a></p> <ul> <li><p><a href="#heading-mixed-import-files">Mixed Import Files</a></p> </li> <li><p><a href="#heading-conditional-imports-and-platform-specific-files">Conditional Imports and Platform-Specific Files</a></p> </li> <li><p><a href="#heading-generated-files">Generated Files</a></p> </li> </ul> </li> <li><p><a href="#heading-the-materialuicompatibilitybridge-bridging-the-gap">The MaterialUiCompatibilityBridge: Bridging the Gap</a></p> <ul> <li><a href="#heading-when-to-use-the-compatibility-bridge">When to Use the Compatibility Bridge</a></li> </ul> </li> <li><p><a href="#heading-localizations-what-changed-and-how-to-update">Localizations: What Changed and How to Update</a></p> <ul> <li><p><a href="#heading-the-old-localizations-setup">The Old Localizations Setup</a></p> </li> <li><p><a href="#heading-the-new-localizations-setup">The New Localizations Setup</a></p> </li> <li><p><a href="#heading-localizations-architecture-diagram">Localizations Architecture Diagram</a></p> </li> </ul> </li> <li><p><a href="#heading-before-and-after-side-by-side-code-comparisons">Before and After: Side by Side Code Comparisons</a></p> <ul> <li><p><a href="#heading-a-basic-app-setup">A Basic App Setup</a></p> </li> <li><p><a href="#heading-a-screen-with-material-widgets">A Screen With Material Widgets</a></p> </li> <li><p><a href="#heading-a-cupertino-screen">A Cupertino Screen</a></p> </li> <li><p><a href="#heading-an-app-that-uses-both-material-and-cupertino">An App That Uses Both Material and Cupertino</a></p> </li> </ul> </li> <li><p><a href="#heading-migrating-package-authors">Migrating Package Authors</a></p> <ul> <li><p><a href="#heading-what-to-do-as-a-package-author">What to Do as a Package Author</a></p> </li> <li><p><a href="#heading-maintaining-backward-compatibility-during-the-transition">Maintaining Backward Compatibility During the Transition</a></p> </li> <li><p><a href="#heading-checking-your-pubdev-score">Checking Your pub.dev Score</a></p> </li> </ul> </li> <li><p><a href="#heading-what-else-changed-in-flutter-347">What Else Changed in Flutter 3.47</a></p> <ul> <li><p><a href="#heading-impeller-is-now-the-default-on-desktop">Impeller Is Now the Default on Desktop</a></p> </li> <li><p><a href="#heading-minimum-ios-and-macos-versions-raised">Minimum iOS and macOS Versions Raised</a></p> </li> <li><p><a href="#heading-ios-uiscene-lifecycle-mandate">iOS UIScene Lifecycle Mandate</a></p> </li> <li><p><a href="#heading-widget-previews-graduate-to-stable">Widget Previews Graduate to Stable</a></p> </li> <li><p><a href="#heading-webassembly-getting-closer-to-default">WebAssembly Getting Closer to Default</a></p> </li> </ul> </li> <li><p><a href="#heading-deprecation-timeline-when-the-old-imports-stop-working">Deprecation Timeline: When the Old Imports Stop Working</a></p> </li> <li><p><a href="#heading-best-practices">Best Practices</a></p> <ul> <li><p><a href="#heading-migrate-early-migrate-once">Migrate Early, Migrate Once</a></p> </li> <li><p><a href="#heading-remove-flutterlocalizations-after-migrating">Remove flutterlocalizations After Migrating</a></p> </li> <li><p><a href="#heading-use-the-compatibility-bridge-temporarily-not-permanently">Use the Compatibility Bridge Temporarily, Not Permanently</a></p> </li> <li><p><a href="#heading-pin-your-material-and-cupertino-package-versions-in-ci">Pin Your Material and Cupertino Package Versions in CI</a></p> </li> </ul> </li> <li><p><a href="#heading-common-mistakes">Common Mistakes</a></p> <ul> <li><p><a href="#heading-mixing-old-and-new-imports-in-the-same-file">Mixing Old and New Imports in the Same File</a></p> </li> <li><p><a href="#heading-forgetting-the-compatibility-bridge-when-needed">Forgetting the Compatibility Bridge When Needed</a></p> </li> <li><p><a href="#heading-running-pub-get-after-dart-fix-without-adding-the-packages-first">Running pub get After dart fix Without Adding the Packages First</a></p> </li> <li><p><a href="#heading-not-bumping-the-major-version-when-migrating-a-package">Not Bumping the Major Version When Migrating a Package</a></p> </li> <li><p><a href="#heading-expecting-widgets-to-behave-differently-after-migration">Expecting Widgets to Behave Differently After Migration</a></p> </li> </ul> </li> <li><p><a href="#heading-conclusion">Conclusion</a></p> </li> <li><p><a href="#heading-references">References</a></p> </li> </ul> <h2 id="heading-prerequisites">Prerequisites</h2> <p>Before working through this guide, make sure the following are in place.</p> <p><strong>Flutter 3.47 or higher:</strong> This guide covers features that exist only in this release. Run <code>flutter upgrade</code> in your terminal to get there, then verify with <code>flutter --version</code>.</p> <p><strong>Dart SDK 3.10 or higher:</strong> Dart 3.10 ships with Flutter 3.47. Verify with <code>dart --version</code>.</p> <p><strong>An existing Flutter project or a willingness to follow the migration steps in a sandbox:</strong> The migration concepts apply to any Flutter app regardless of its size.</p> <p><strong>Basic familiarity with Flutter project structure:</strong> You should know what <code>pubspec.yaml</code> is, what <code>flutter pub get</code> does, and what an import statement in Dart looks like.</p> <p><strong>No prior knowledge of the decoupling feature required:</strong> This guide explains everything from the beginning. But reading <a href="https://www.freecodecamp.org/news/decoupling-material-and-cupertino-in-flutter/">Decoupling Material and Cupertino in Flutter</a> first gives you useful background context on the motivation for the change.</p> <h2 id="heading-what-changed-and-why-it-matters-the-full-picture">What Changed and Why It Matters: The Full Picture</h2> <p>Before Flutter 3.47, when you wrote <code>import 'package:flutter/material.dart'</code>, you were importing the Material widget library that was baked directly into the Flutter SDK. You couldn't get a newer version of Material widgets without upgrading the entire Flutter SDK. You had no choice in the matter.</p> <p>After Flutter 3.47, Material and Cupertino are their own packages on pub.dev: <code>material_ui</code> and <code>cupertino_ui</code>. You can upgrade them independently of the Flutter SDK. They ship bug fixes and new components on their own weekly schedules. And the Flutter SDK no longer owns their development roadmap.</p> <h3 id="heading-why-the-flutter-team-did-this">Why the Flutter Team Did This</h3> <p>The original architecture made sense in 2018 when Flutter launched. Bundling Material and Cupertino directly into the SDK meant developers always had them available without any configuration. It was simple to get started with, and had zero friction.</p> <p>But as Flutter matured, the bundling became a constraint. The Material Design 3 rollout was slower than it should have been because every Material change had to wait for a quarterly SDK release. Community contributors found it harder to get widget improvements merged because the bar for touching core SDK code is high. Teams using Flutter for entirely custom design systems still pulled in Material and Cupertino as transitive dependencies whether they wanted them or not.</p> <p>The decoupling fixes all three problems. Teams that use Material widgets can get fixes and new components weekly instead of quarterly. Teams building custom design systems don't have to carry Material as a dependency. And the path is clear toward a genuinely style-neutral Flutter core, where the framework handles layout, rendering, and platform interaction, while design libraries are entirely optional and swappable.</p> <h3 id="heading-the-impact-on-your-current-code">The Impact on Your Current Code</h3> <p>Your existing code continues to compile in Flutter 3.47. The old <code>package:flutter/material.dart</code> and <code>package:flutter/cupertino.dart</code> imports still work for now. Nothing breaks the moment you upgrade to Flutter 3.47.</p> <p>The deprecation is scheduled for the Fall 2026 stable release, expected in November. That's when the old bundled imports will be formally deprecated. They won't be removed immediately after deprecation, but the clock has started.</p> <h2 id="heading-understanding-the-old-architecture">Understanding the Old Architecture</h2> <img src="https://cdn.hashnode.com/uploads/covers/63a47b24490dd1c9cd9c32ff/f60be998-1d92-462f-85f5-1a5feb2df1b0.png" alt="Old Flutter architecture before version 3.47. The Flutter SDK is shown as one bundled package containing Material widgets, Cupertino widgets, the base widget layer, rendering, painting, platform services, and localization. The diagram highlights five problems: Material fixes require an SDK release, custom design systems still depend on Material, contributing to the core SDK is difficult, components cannot be independently versioned, and Material and Cupertino share the same release cycle." style="display:block;margin:0 auto" width="600" height="400" loading="lazy"> <p>Before Flutter 3.47, major Flutter UI components were bundled inside the Flutter SDK and released together. Material Design, Cupertino, widgets, rendering, painting, platform services, and localization all lived within the same SDK release structure.</p> <p>This created several limitations. A Material bug fix could require waiting for a Flutter SDK release. Teams building their own design systems could still be tied to Material. Contributing changes to the core SDK had a higher barrier, making improvements slower. Material couldn't be versioned independently from the underlying Flutter SDK, and Material and Cupertino followed the same release cadence even when only one of them needed an urgent update.</p> <p>The old architecture tightly coupled Flutter's UI libraries to the SDK, so individual components couldn't evolve and release as independently as they could in a more modular architecture.</p> <p>Every Flutter project that used <code>package:flutter/material.dart</code> was tightly coupled to the SDK's release schedule. If Material introduced a visual bug, you waited for the next quarterly SDK release to get the fix, even if the Flutter engine itself had no issues. This tight coupling was the fundamental problem the decoupling initiative was designed to solve.</p> <h2 id="heading-the-new-architecture-standalone-packages">The New Architecture: Standalone Packages</h2> <img src="https://cdn.hashnode.com/uploads/covers/63a47b24490dd1c9cd9c32ff/27d282b4-7cb3-42a1-9773-dfd99a1fb380.png" alt="New Flutter architecture from Flutter 3.47 onward. Material UI and Cupertino UI are separated into independent packages on pub.dev, each with its own versioning and weekly releases. Both packages depend on the Flutter SDK core, which now contains only the base widget, rendering, painting, services, and foundation layers and continues to release quarterly. The architecture enables faster UI fixes, optional Material usage, easier contributions, independent versioning, and a more style-neutral Flutter core." style="display:block;margin:0 auto" width="600" height="400" loading="lazy"> <p>Starting with Flutter 3.47, the architecture separates Flutter's design systems from the core SDK. Material UI and Cupertino UI are independent packages published through <a href="http://pub.dev">pub.dev</a>. Each package can have its own version and release updates independently.</p> <p>Both packages depend on the <strong>Flutter SDK core</strong>, which contains the underlying widget, rendering, painting, platform services, and foundation layers. The core SDK remains on its regular quarterly release cycle, while the UI packages can ship updates more frequently.</p> <p>Flutter's core is becoming more modular. Material and Cupertino can evolve independently without requiring the entire Flutter SDK to be released.</p> <p>The key architectural insight is the separation of concerns. The Flutter SDK now owns the rendering engine, the base widget layer, and the platform abstractions. The design systems (<code>material_ui</code> and <code>cupertino_ui</code>) are first-party packages on pub.dev, owned by the Flutter team but versioned and released independently.</p> <h2 id="heading-setting-up-adding-the-new-packages">Setting Up: Adding the New Packages</h2> <h3 id="heading-adding-materialui">Adding material_ui</h3> <pre><code class="language-bash">flutter pub add material_ui </code></pre> <p>This single command adds <code>material_ui</code> to your <code>pubspec.yaml</code> under <code>dependencies</code> and runs <code>flutter pub get</code> automatically. After running it, your <code>pubspec.yaml</code> will contain:</p> <pre><code class="language-yaml">dependencies: flutter: sdk: flutter material_ui: ^1.0.0 </code></pre> <p><code>flutter pub add material_ui</code> is the idiomatic way to add a package. It automatically selects the latest compatible version and adds the correct constraint format. The <code>^1.0.0</code> constraint means "1.0.0 or any higher version that is compatible with 1.x", following Dart's semver conventions.</p> <p>This is the constraint you want: it allows patch and minor updates to land automatically when you run <code>flutter pub upgrade</code>, but it prevents breaking changes from a hypothetical <code>2.0.0</code> from disrupting your project.</p> <h3 id="heading-adding-cupertinoui">Adding cupertino_ui</h3> <pre><code class="language-bash">flutter pub add cupertino_ui </code></pre> <p>Add this only if your project uses Cupertino-style widgets. Apps that target only Android or that use purely custom design systems may not need it.</p> <pre><code class="language-yaml">dependencies: flutter: sdk: flutter material_ui: ^1.0.0 cupertino_ui: ^1.0.0 </code></pre> <h3 id="heading-adding-both-at-once">Adding Both at Once</h3> <pre><code class="language-bash">flutter pub add material_ui cupertino_ui </code></pre> <p>Listing both package names in a single <code>flutter pub add</code> command adds them together and resolves the full dependency graph once, which is faster than running two separate commands.</p> <h2 id="heading-migrating-your-project-the-automated-path">Migrating Your Project: The Automated Path</h2> <p>The Flutter team ships a migration tool that handles the most common cases automatically. For most projects, this is the complete migration.</p> <h3 id="heading-step-1-run-the-migration-tool">Step 1: Run the Migration Tool</h3> <pre><code class="language-bash">dart fix --apply --code=migrate_design_widgets </code></pre> <p><code>dart fix</code> is Dart's built-in automated code repair tool. <code>--apply</code> tells it to apply all suggested fixes without asking for confirmation on each one. <code>--code=migrate_design_widgets</code> runs specifically the <code>migrate_design_widgets</code> fix, which is the new code fix that handles the decoupling migration. It scans your project for <code>package:flutter/material.dart</code> and <code>package:flutter/cupertino.dart</code> imports and updates them to the correct new import from <code>package:material_ui/material_ui.dart</code> and <code>package:cupertino_ui/cupertino_ui.dart</code>, respectively.</p> <p>The tool also attempts to update your <code>pubspec.yaml</code> to add the new package dependencies. There's a known early bug where the <code>pubspec.yaml</code> update may not apply correctly in some cases.</p> <h3 id="heading-step-2-handle-the-known-pubspecyaml-bug">Step 2: Handle the Known pubspec.yaml Bug</h3> <p>If the migration tool didn't successfully update your <code>pubspec.yaml</code>, run:</p> <pre><code class="language-bash">flutter pub add material_ui flutter pub add cupertino_ui dart fix --apply </code></pre> <p><code>flutter pub add material_ui</code> and <code>flutter pub add cupertino_ui</code> add the packages manually to <code>pubspec.yaml</code> and run the package resolution. Then <code>dart fix --apply</code> (without the <code>--code</code> flag this time) applies any remaining fixes that the initial run may have missed now that the packages are available.</p> <p>Running <code>dart fix</code> after the packages are in <code>pubspec.yaml</code> allows it to validate the import paths against the actual installed packages.</p> <h3 id="heading-step-3-verify-the-migration">Step 3: Verify the Migration</h3> <pre><code class="language-bash">flutter analyze </code></pre> <p><code>flutter analyze</code> runs the Dart analyzer across your entire project and reports any remaining issues. After a successful migration, you should see no errors related to missing imports or deprecated APIs. If errors remain, they fall into one of two categories: imports that the migration tool couldn't automatically update (covered in the manual path section below), or dependencies on third-party packages that haven't yet migrated (covered in the compatibility bridge section).</p> <h3 id="heading-what-the-tool-actually-changes">What the Tool Actually Changes</h3> <p>Here's exactly what the automated migration does to your import statements:</p> <pre><code class="language-dart">// BEFORE: What every Flutter app used to write import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; </code></pre> <pre><code class="language-dart">// AFTER: What the migration tool produces import 'package:material_ui/material_ui.dart'; import 'package:cupertino_ui/cupertino_ui.dart'; </code></pre> <p>The <code>import 'package:flutter/material.dart'</code> statement imported the Material library from the bundled location inside the Flutter SDK. The <code>import 'package:material_ui/material_ui.dart'</code> statement imports from the standalone package you added in <code>pubspec.yaml</code>.</p> <p>The widget names, class names, and API surface are identical. <code>Scaffold</code> is still <code>Scaffold</code>. <code>ThemeData</code> is still <code>ThemeData</code>. <code>AppBar</code> is still <code>AppBar</code>. No widgets were renamed or restructured. The only change is the import path.</p> <p>The reason this migration is possible with a simple find-and-replace on import paths is that the Flutter team deliberately designed <code>material_ui</code> to be a drop-in replacement for the bundled Material library. The API surface is frozen at the same state the bundled library was in when the freeze happened. This is also why the package README says contributions were frozen in April to ensure a smooth migration.</p> <p>What you get in <code>material_ui</code> 1.0 is exactly what you had in <code>package:flutter/material.dart</code> in Flutter 3.44, with the path to receive further improvements on a faster cadence going forward.</p> <h2 id="heading-migrating-your-project-the-manual-path">Migrating Your Project: The Manual Path</h2> <p>The automated tool handles the vast majority of migrations. But there are specific cases where manual intervention is needed.</p> <h3 id="heading-mixed-import-files">Mixed Import Files</h3> <p>If you have a file that imports from multiple Flutter sub-libraries on the same line or in ways the tool can't parse:</p> <pre><code class="language-dart">// A file with multiple flutter imports import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/gestures.dart'; </code></pre> <p>The tool updates only the <code>material.dart</code> import. The others remain pointing to <code>package:flutter/...</code> because <code>rendering.dart</code>, <code>services.dart</code>, and <code>gestures.dart</code> are core framework libraries that don't move to standalone packages. They stay exactly where they are. Only the design-system imports change.</p> <pre><code class="language-dart">// After migration: correct state import 'package:material_ui/material_ui.dart'; // Updated import 'package:flutter/rendering.dart'; // Stays the same import 'package:flutter/services.dart'; // Stays the same import 'package:flutter/gestures.dart'; // Stays the same </code></pre> <p><code>package:flutter/rendering.dart</code> and similar core framework imports don't move because they're part of the SDK's own domain: layout, rendering, painting, and platform services. The decoupling is specifically about design systems, not the underlying framework primitives. This distinction is important to understand so you don't accidentally try to find a <code>rendering_ui</code> package that doesn't exist.</p> <h3 id="heading-conditional-imports-and-platform-specific-files">Conditional Imports and Platform-Specific Files</h3> <pre><code class="language-dart">// Platform-specific file that used conditional imports export 'package:flutter/material.dart' if (dart.library.html) 'package:flutter/material.dart'; </code></pre> <p>Update both sides of conditional imports manually:</p> <pre><code class="language-dart">// After migration export 'package:material_ui/material_ui.dart' if (dart.library.html) 'package:material_ui/material_ui.dart'; </code></pre> <p>Conditional imports with <code>if (dart.library...)</code> select between two import paths based on the platform at compile time. The migration tool may not correctly handle both branches of a conditional import in all cases. Manually verify any file in your project that contains <code>if (dart.library.html)</code> or similar platform conditions on import statements.</p> <h3 id="heading-generated-files">Generated Files</h3> <p>Files ending in <code>.g.dart</code>, <code>.freezed.dart</code>, or other generated suffixes are produced by build_runner and should never be manually edited. They'll regenerate with the correct imports when you run:</p> <pre><code class="language-bash">dart run build_runner build --delete-conflicting-outputs </code></pre> <p><code>dart run build_runner build</code> executes all code generators (json_serializable, freezed, riverpod_generator, and so on) against your source files. <code>--delete-conflicting-outputs</code> removes previously generated files before regenerating, which prevents stale generated code from causing conflicts.</p> <p>Because the source <code>.dart</code> files now have updated imports from the migration tool, the generators re-read those source files and produce generated files with consistent imports. There's nothing special to do for generated files beyond running the generators again after the migration.</p> <h2 id="heading-the-materialuicompatibilitybridge-bridging-the-gap">The MaterialUiCompatibilityBridge: Bridging the Gap</h2> <p>The ecosystem doesn't migrate overnight. When you update your app to use <code>material_ui</code>, some of your third-party package dependencies may still be using <code>package:flutter/material.dart</code> internally. This creates a situation where your app's widget tree has widgets from two different sources of Material: the new standalone package and the old bundled one.</p> <p>The <code>MaterialUiCompatibilityBridge</code> exists to handle exactly this situation. It provides a compatibility layer that allows both sources of Material widgets to coexist in the same widget tree without runtime errors.</p> <pre><code class="language-dart">import 'package:material_ui/material_ui.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF6750A4), ), ), builder: (BuildContext context, Widget? child) { return MaterialUiCompatibilityBridge(child: child!); }, home: const HomeScreen(), ); } } </code></pre> <p><code>import 'package:material_ui/material_ui.dart'</code> is the new import. All Material widgets including <code>MaterialApp</code>, <code>ThemeData</code>, <code>ColorScheme</code>, and <code>MaterialUiCompatibilityBridge</code> are available from this single import.</p> <p><code>MaterialApp(...)</code> is unchanged in name and behavior from what you used before. The same constructor parameters, the same behavior. The class comes from <code>material_ui</code> now instead of the bundled SDK, but your code that uses it doesn't change.</p> <p><code>builder: (BuildContext context, Widget? child) { return MaterialUiCompatibilityBridge(child: child!); }</code> is the compatibility layer insertion. The <code>builder</code> parameter of <code>MaterialApp</code> wraps the entire widget tree that <code>MaterialApp</code> creates. By inserting <code>MaterialUiCompatibilityBridge</code> at this level, it sits above every widget in your app. This means any widget anywhere in the tree, whether it comes from your code (using <code>material_ui</code>) or from a dependency (still using <code>package:flutter/material.dart</code>), operates under the bridge's compatibility context.</p> <p>The <code>child!</code> with the null assertion is safe here because <code>MaterialApp</code> always provides a non-null child to the builder when the app has a <code>home</code>, <code>routes</code>, or <code>initialRoute</code> configured.</p> <h3 id="heading-when-to-use-the-compatibility-bridge">When to Use the Compatibility Bridge</h3> <img src="https://cdn.hashnode.com/uploads/covers/63a47b24490dd1c9cd9c32ff/9d2d4b70-c9c2-40f0-9b09-5e5f8371c252.png" alt="Compatibility Bridge Decision Tree. The diagram asks whether a project has dependencies that use Material widgets. If the answer is No, the project does not need the compatibility bridge. If the answer is Yes, the next question asks whether all those dependencies have been updated to use material_ui. If all have been updated, the bridge is not needed. If some or none have been updated, the project should use the compatibility bridge." style="display:block;margin:0 auto" width="600" height="400" loading="lazy"> <p>Start with one question: <strong>Does your project have dependencies that use Material widgets?</strong></p> <p><strong>No:</strong> You don't need the compatibility bridge. You can proceed without it.</p> <p><strong>Yes:</strong> Check whether those dependencies have been updated to use <code>material_ui</code>.</p> <ul> <li><p><strong>All of them:</strong> The bridge isn't needed. Proceed without it.</p> </li> <li><p><strong>Some or none:</strong> Use the compatibility bridge while those dependencies are being updated.</p> </li> </ul> <p>The bridge is only necessary when your project still relies on dependencies that use the old Material widgets. If everything has already moved to <code>material_ui</code>, you can remove or avoid the bridge.</p> <p>It's a transitional tool. As the ecosystem migrates, you can check whether your dependencies have updated by running:</p> <pre><code class="language-bash">flutter pub outdated </code></pre> <p>When all your dependencies use <code>material_ui</code>, remove the bridge. It's not intended to be a permanent part of your app.</p> <h2 id="heading-localizations-what-changed-and-how-to-update">Localizations: What Changed and How to Update</h2> <p>Localizations are one of the most significant practical changes in this migration. The <code>flutter_localizations</code> package previously provided translations and localization delegates for both Material and Cupertino widgets as a single bundled package. That's now split across the two standalone packages.</p> <h3 id="heading-the-old-localizations-setup">The Old Localizations Setup</h3> <pre><code class="language-dart">// BEFORE: The old way with flutter_localizations import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter/material.dart'; MaterialApp( localizationsDelegates: const &lt;LocalizationsDelegate&lt;dynamic&gt;&gt;[ GlobalCupertinoLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: const [ Locale('en'), Locale('ar'), Locale('fr'), ], // ... ) </code></pre> <p>The old approach required explicitly listing three delegates: <code>GlobalCupertinoLocalizations.delegate</code> for Cupertino widget strings, <code>GlobalMaterialLocalizations.delegate</code> for Material widget strings, and <code>GlobalWidgetsLocalizations.delegate</code> for base widget strings. You also needed the separate <code>flutter_localizations</code> import. This was verbose and required developers to know which delegate covered which widgets.</p> <h3 id="heading-the-new-localizations-setup">The New Localizations Setup</h3> <pre><code class="language-dart">// AFTER: The new way with material_ui import 'package:material_ui/material_ui.dart'; MaterialApp( localizationsDelegates: GlobalMaterialLocalizations.delegates, supportedLocales: const [ Locale('en'), Locale('ar'), Locale('fr'), ], // ... ) </code></pre> <p><code>GlobalMaterialLocalizations.delegates</code> is a getter that returns all three delegates together: the Material delegate, the Cupertino delegate, and the Widgets delegate. By assigning this single getter to <code>localizationsDelegates</code>, you get the same coverage as the old three-delegate list with less code.</p> <p>The Cupertino strings are included automatically even if you don't separately import <code>cupertino_ui</code>, because <code>material_ui</code> depends on <code>cupertino_ui</code> internally and bundles those localization delegates in its combined getter.</p> <p>The separate <code>flutter_localizations</code> import is no longer needed. The package still exists (it's not deprecated), but for projects migrating to <code>material_ui</code>, you can remove it from both your import statements and your <code>pubspec.yaml</code> dependencies.</p> <h3 id="heading-localizations-architecture-diagram">Localizations Architecture Diagram</h3> <img src="https://cdn.hashnode.com/uploads/covers/63a47b24490dd1c9cd9c32ff/1373bd32-83f8-4001-bed0-de8968eb6b8f.png" alt="Localization Architecture: Before and After. Before, Flutter localization used a separate flutter_localizations package, requiring developers to explicitly register Material, Cupertino, and Widgets localization delegates. After, material_ui provides GlobalMaterialLocalizations.delegates, which includes the required Cupertino and Widgets delegates automatically." style="display:block;margin:0 auto" width="600" height="400" loading="lazy"> <p>The diagram compares Flutter's localization setup before and after the architectural change.</p> <p><strong>Before:</strong> Localization was provided through the separate <code>flutter_localizations</code> package. Developers had to explicitly include the Material, Cupertino, and Widgets localization delegates.</p> <p><strong>After:</strong> Localization is simplified through the <code>material_ui</code> package. <code>GlobalMaterialLocalizations.delegates</code> provides the delegates together, with Cupertino and Widgets localization included automatically.</p> <p>The new approach reduces the amount of localization configuration developers need to write and makes the setup easier to maintain.</p> <h2 id="heading-before-and-after-side-by-side-code-comparisons">Before and After: Side by Side Code Comparisons</h2> <h3 id="heading-a-basic-app-setup">A Basic App Setup</h3> <pre><code class="language-dart">// BEFORE: Standard Flutter app entry point import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'My App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: const [Locale('en')], home: const HomeScreen(), ); } } </code></pre> <pre><code class="language-dart">// AFTER: Migrated app entry point import 'package:material_ui/material_ui.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'My App', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), localizationsDelegates: GlobalMaterialLocalizations.delegates, supportedLocales: const [Locale('en')], home: const HomeScreen(), ); } } </code></pre> <p>The diff here is three changes: the import line changes from <code>package:flutter/material.dart</code> to <code>package:material_ui/material_ui.dart</code>, the <code>flutter_localizations</code> import is removed, and the <code>localizationsDelegates</code> list collapses from three explicit delegates to one getter. Everything else (<code>MaterialApp</code>, <code>ThemeData</code>, <code>ColorScheme.fromSeed</code>, <code>useMaterial3</code>, and <code>home</code>) is identical because the API didn't change.</p> <h3 id="heading-a-screen-with-material-widgets">A Screen With Material Widgets</h3> <pre><code class="language-dart">// BEFORE import 'package:flutter/material.dart'; class ProfileScreen extends StatelessWidget { const ProfileScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Profile'), backgroundColor: Theme.of(context).colorScheme.inversePrimary, ), body: ListView( padding: const EdgeInsets.all(16), children: [ Card( child: ListTile( leading: const CircleAvatar(child: Icon(Icons.person)), title: const Text('Ade Mensah'), subtitle: const Text('Flutter Developer'), trailing: const Icon(Icons.chevron_right), ), ), const SizedBox(height: 16), FilledButton( onPressed: () {}, child: const Text('Edit Profile'), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () {}, child: const Icon(Icons.add), ), ); } } </code></pre> <pre><code class="language-dart">// AFTER: Migrated screen import 'package:material_ui/material_ui.dart'; class ProfileScreen extends StatelessWidget { const ProfileScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Profile'), backgroundColor: Theme.of(context).colorScheme.inversePrimary, ), body: ListView( padding: const EdgeInsets.all(16), children: [ Card( child: ListTile( leading: const CircleAvatar(child: Icon(Icons.person)), title: const Text('Ade Mensah'), subtitle: const Text('Flutter Developer'), trailing: const Icon(Icons.chevron_right), ), ), const SizedBox(height: 16), FilledButton( onPressed: () {}, child: const Text('Edit Profile'), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () {}, child: const Icon(Icons.add), ), ); } } </code></pre> <p>The widget tree is completely identical. <code>Scaffold</code>, <code>AppBar</code>, <code>Card</code>, <code>ListTile</code>, <code>CircleAvatar</code>, <code>FilledButton</code>, and <code>FloatingActionButton</code>: every widget name, parameter, and behavior is unchanged.</p> <p>The only line that differs is the import at the top. This is by design. The Flutter team's explicit goal was to make the migration a pure import change with zero widget API changes.</p> <h3 id="heading-a-cupertino-screen">A Cupertino Screen</h3> <pre><code class="language-dart">// BEFORE import 'package:flutter/cupertino.dart'; class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key}); @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar( middle: Text('Settings'), ), child: SafeArea( child: CupertinoListSection.insetGrouped( children: [ CupertinoListTile( title: const Text('Notifications'), leading: const Icon(CupertinoIcons.bell), trailing: CupertinoSwitch( value: true, onChanged: (value) {}, ), ), ], ), ), ); } } </code></pre> <pre><code class="language-dart">// AFTER import 'package:cupertino_ui/cupertino_ui.dart'; class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key}); @override Widget build(BuildContext context) { return CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar( middle: Text('Settings'), ), child: SafeArea( child: CupertinoListSection.insetGrouped( children: [ CupertinoListTile( title: const Text('Notifications'), leading: const Icon(CupertinoIcons.bell), trailing: CupertinoSwitch( value: true, onChanged: (value) {}, ), ), ], ), ), ); } } </code></pre> <p>Same story. <code>CupertinoPageScaffold</code>, <code>CupertinoNavigationBar</code>, <code>CupertinoListSection</code>, <code>CupertinoListTile</code>, <code>CupertinoSwitch</code>, and <code>CupertinoIcons</code> are all available from <code>package:cupertino_ui/cupertino_ui.dart</code> exactly as they were from <code>package:flutter/cupertino.dart</code>. One import line changes, zero widget code changes.</p> <h3 id="heading-an-app-that-uses-both-material-and-cupertino">An App That Uses Both Material and Cupertino</h3> <p>Some apps mix design systems. A common pattern is using Cupertino dialogs and pickers inside a primarily Material app. Both libraries are available simultaneously with no conflicts:</p> <pre><code class="language-dart">// BEFORE import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; class DatePickerButton extends StatelessWidget { const DatePickerButton({super.key}); void _showDatePicker(BuildContext context) { showCupertinoModalPopup( context: context, builder: (context) =&gt; Container( height: 216, color: CupertinoColors.systemBackground, child: CupertinoDatePicker( mode: CupertinoDatePickerMode.date, onDateTimeChanged: (DateTime newDate) {}, ), ), ); } @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () =&gt; _showDatePicker(context), child: const Text('Pick Date'), ); } } </code></pre> <pre><code class="language-dart">// AFTER: Both packages imported import 'package:material_ui/material_ui.dart'; import 'package:cupertino_ui/cupertino_ui.dart'; class DatePickerButton extends StatelessWidget { const DatePickerButton({super.key}); void _showDatePicker(BuildContext context) { showCupertinoModalPopup( context: context, builder: (context) =&gt; Container( height: 216, color: CupertinoColors.systemBackground, child: CupertinoDatePicker( mode: CupertinoDatePickerMode.date, onDateTimeChanged: (DateTime newDate) {}, ), ), ); } @override Widget build(BuildContext context) { return ElevatedButton( onPressed: () =&gt; _showDatePicker(context), child: const Text('Pick Date'), ); } } </code></pre> <p>Both <code>material_ui</code> and <code>cupertino_ui</code> can be imported in the same file without any namespace conflicts. Note that <code>material_ui</code> already depends on <code>cupertino_ui</code> internally, so in practice you may find you don't need to explicitly import <code>cupertino_ui</code> in most files because the Cupertino types are accessible through the Material import. But explicitly importing both is clearer about intent and is the recommended practice for files that meaningfully use widgets from both systems.</p> <h2 id="heading-migrating-package-authors">Migrating Package Authors</h2> <p>If you maintain a Flutter package (not just a Flutter app), the migration has additional considerations. The Flutter team explicitly states: treat this move to the standalone packages as a major release of your package.</p> <h3 id="heading-what-to-do-as-a-package-author">What to Do as a Package Author</h3> <pre><code class="language-yaml"># Your package's pubspec.yaml BEFORE migration name: my_flutter_package version: 1.5.0 dependencies: flutter: sdk: flutter </code></pre> <pre><code class="language-yaml"># Your package's pubspec.yaml AFTER migration name: my_flutter_package version: 2.0.0 dependencies: flutter: sdk: flutter material_ui: ^1.0.0 </code></pre> <p>The version bump to <code>2.0.0</code> is required because this is a breaking change for your package's consumers. Before, importing your package didn't require <code>material_ui</code> in the consumer's project (it came bundled). After, your package declares an explicit dependency on <code>material_ui</code>, which changes your package's dependency graph. Consumers updating to your <code>2.0.0</code> will need to also have <code>material_ui</code> available, which they will if they're also migrating. The semver major bump communicates this clearly.</p> <h3 id="heading-maintaining-backward-compatibility-during-the-transition">Maintaining Backward Compatibility During the Transition</h3> <p>If you want to support both old and new Flutter setups during the transition period (before November 2026), you can use Dart's conditional export feature:</p> <pre><code class="language-dart">// lib/src/widgets.dart // This is the internal file that handles the conditional import export 'package:material_ui/material_ui.dart' if (dart.library.nonexistent) 'package:flutter/material.dart'; </code></pre> <p>But this approach is complex and rarely necessary. The Flutter team's recommendation is simpler: migrate your package to <code>material_ui</code>, bump the major version, and let your users upgrade at their own pace. The compatibility bridge in <code>material_ui</code> handles the consumer-side coexistence for users who are in the middle of migrating their own apps.</p> <h3 id="heading-checking-your-pubdev-score">Checking Your pub.dev Score</h3> <p>After migrating your package to <code>material_ui</code>, the static analysis that powers pub.dev scores will recognize the migration and reward it appropriately. The tooling now flags packages that haven't migrated with a lower pub points score. This is an intentional incentive structure to drive ecosystem adoption.</p> <h2 id="heading-what-else-changed-in-flutter-347">What Else Changed in Flutter 3.47</h2> <p>The decoupling is the headline feature, but Flutter 3.47 brings several other significant changes that affect real projects.</p> <h3 id="heading-impeller-is-now-the-default-on-desktop">Impeller Is Now the Default on Desktop</h3> <p>Impeller, Flutter's next-generation rendering engine that was already default on iOS and Android, is now the default renderer for macOS, Windows, and Linux. Impeller eliminates shader compilation jank (the brief stutter the first time an animation plays) by compiling shaders at build time rather than at runtime.</p> <p>For most projects, this is a transparent improvement. Your animations will be smoother from the very first frame. If you encounter rendering issues and need to temporarily disable Impeller:</p> <pre><code class="language-xml">&lt;!-- macOS: ios/Runner/Info.plist --&gt; &lt;key&gt;FLTEnableImpeller&lt;/key&gt; &lt;false/&gt; </code></pre> <pre><code class="language-cpp">// Windows: windows/runner/main.cpp project.set_impeller_switch(flutter::ImpellerSwitch::Disabled); </code></pre> <pre><code class="language-c">// Linux: linux/my_application.cc fl_dart_project_set_enable_impeller(project, FALSE); </code></pre> <p>These opt-out mechanisms exist for projects that find bugs with the new default. The fallback to Skia will be removed in a future release, so if you must opt out, file a bug report with the Flutter team so the underlying issue can be fixed.</p> <h3 id="heading-minimum-ios-and-macos-versions-raised">Minimum iOS and macOS Versions Raised</h3> <p>With Xcode 27 support, the minimum supported OS versions have changed:</p> <pre><code class="language-plaintext">Platform Previous Minimum New Minimum (Flutter 3.47+) iOS 13 15 macOS 10.15 (Catalina) 12 (Monterey) </code></pre> <p>If your app's <code>ios/Runner.xcodeproj</code> or <code>macos/Runner.xcodeproj</code> specifies deployment targets below these new minimums, the build will fail. Update your deployment targets in Xcode, or let the Flutter CLI handle it automatically by running <code>flutter build ios</code> which will warn you about the mismatch.</p> <h3 id="heading-ios-uiscene-lifecycle-mandate">iOS UIScene Lifecycle Mandate</h3> <p>Apps built with Xcode 27 that use the legacy <code>UIApplication</code> delegate lifecycle (rather than the newer <code>UIScene</code> lifecycle) will fail to launch on iOS 27. For most Flutter apps, the CLI handles this migration automatically during the build.</p> <p>If your app has custom native code in <code>AppDelegate.swift</code> or <code>AppDelegate.m</code>, or uses plugins that rely on the legacy lifecycle, you need to migrate manually by following the UIScene/Delegate Adoption Guide in the Flutter documentation.</p> <h3 id="heading-widget-previews-graduate-to-stable">Widget Previews Graduate to Stable</h3> <p>Widget Previews, which let you render individual widgets without building the full app, are now stable. A <code>.widget_preview/</code> folder at the project root caches preview state for faster startup. This is worth enabling if your team iterates heavily on widget UI.</p> <h3 id="heading-webassembly-getting-closer-to-default">WebAssembly Getting Closer to Default</h3> <p>Wasm isn't yet the default for Flutter Web, but it's getting closer. You can opt in now:</p> <pre><code class="language-bash">flutter build web --release --wasm </code></pre> <p><code>--wasm</code> builds your Flutter web app targeting WebAssembly instead of JavaScript. The performance improvement is significant for compute-heavy UIs. The prerequisite is that your code and dependencies must use <code>package:web</code> instead of <code>dart:html</code>, since the legacy HTML library isn't supported in Wasm. Most popular packages have already migrated.</p> <h2 id="heading-deprecation-timeline-when-the-old-imports-stop-working">Deprecation Timeline: When the Old Imports Stop Working</h2> <p>Understanding the timeline is critical for planning your migration.</p> <img src="https://cdn.hashnode.com/uploads/covers/63a47b24490dd1c9cd9c32ff/3c1c87c4-5b07-4edf-b577-5fe2298ed4c0.png" alt="Deprecation Timeline. The diagram shows three stages. Flutter 3.47 in August 2026: material_ui and cupertino_ui reach version 1.0, the dart fix migration tool is available, and old imports still work without warnings. Flutter Fall Stable in November 2026: the old Material and Cupertino imports become formally deprecated, analyzer warnings appear, but existing code still runs. A future 2027 release: the old imports are removed and will no longer compile. The recommended action is to migrate before November 2026." style="display:block;margin:0 auto" width="600" height="400" loading="lazy"> <p>The timeline shows the planned transition away from Flutter's old Material and Cupertino imports.</p> <p><strong>August 2026, Flutter 3.47:</strong> The new <code>material_ui</code> and <code>cupertino_ui</code> packages reach version 1.0. The <code>dart fix</code> migration tool is available. Existing imports still work and don't produce deprecation warnings yet. The ecosystem begins moving to the new packages.</p> <p><strong>November 2026, Flutter Fall Stable:</strong> The old <code>package:flutter/material.dart</code> and <code>package:flutter/cupertino.dart</code> imports become formally deprecated. Developers using them will see deprecation warnings in the analyzer. Existing applications will still compile and run during this stage.</p> <p><strong>Future release in 2027:</strong> The old imports are removed from the bundled Flutter SDK. Projects that have not migrated will no longer compile using those imports.</p> <p>The safest time to migrate is now, before November 2026, while the old imports still compile cleanly. Migrating in the deprecation warning period (November 2026 to removal) still works but produces analyzer noise. Migrating after removal requires emergency action, which is avoidable by planning ahead.</p> <h2 id="heading-best-practices">Best Practices</h2> <h3 id="heading-migrate-early-migrate-once">Migrate Early, Migrate Once</h3> <p>The automated migration tool is production-ready. Running it now gives you the benefits of faster Material and Cupertino updates immediately, avoids the deprecation warning period entirely, and puts you ahead of the ecosystem curve.</p> <p>Teams that migrate early also avoid the situation where a dependency upgrade accidentally brings in breaking changes from the new package while they are still using the old one.</p> <h3 id="heading-remove-flutterlocalizations-after-migrating">Remove flutter_localizations After Migrating</h3> <p>After migrating to <code>material_ui</code>, the <code>flutter_localizations</code> package in your <code>pubspec.yaml</code> is redundant. The localization delegates it provided are now included in <code>material_ui</code>. Remove it:</p> <pre><code class="language-yaml"># REMOVE this from pubspec.yaml after migration # flutter_localizations: # sdk: flutter </code></pre> <pre><code class="language-bash"># Also remove the import from all dart files # Remove: import 'package:flutter_localizations/flutter_localizations.dart'; </code></pre> <p>Leaving <code>flutter_localizations</code> in the project doesn't cause errors, but it's unnecessary weight and a potential source of confusion when reading the project's dependencies.</p> <h3 id="heading-use-the-compatibility-bridge-temporarily-not-permanently">Use the Compatibility Bridge Temporarily, Not Permanently</h3> <p>The <code>MaterialUiCompatibilityBridge</code> is a transitional tool. Don't design your architecture around its presence. Add it when you migrate, and set a reminder to remove it when all your dependencies have migrated to <code>material_ui</code>. Check the migration status of your dependencies periodically with:</p> <pre><code class="language-bash">flutter pub outdated </code></pre> <h3 id="heading-pin-your-material-and-cupertino-package-versions-in-ci">Pin Your Material and Cupertino Package Versions in CI</h3> <p>Because <code>material_ui</code> and <code>cupertino_ui</code> now ship weekly updates, you may want to pin specific versions in your CI environment to ensure reproducible builds:</p> <pre><code class="language-yaml"># pubspec.yaml for production stability dependencies: material_ui: 1.2.0 # Exact version pin for CI stability cupertino_ui: 1.1.0 </code></pre> <p>For development, using the <code>^</code> constraint is fine and keeps you current. For CI and production builds, pinning an exact version and upgrading deliberately gives you more control over what changes between builds.</p> <h2 id="heading-common-mistakes">Common Mistakes</h2> <h3 id="heading-mixing-old-and-new-imports-in-the-same-file">Mixing Old and New Imports in the Same File</h3> <pre><code class="language-dart">// WRONG: Both old and new imports in the same file import 'package:flutter/material.dart'; import 'package:material_ui/material_ui.dart'; // Duplicate </code></pre> <p>Having both imports in the same file is redundant and may cause analyzer warnings about duplicate type definitions. After migration, every file should have exactly one Material import: the new <code>package:material_ui/material_ui.dart</code>. Run <code>flutter analyze</code> to catch any files with this issue.</p> <h3 id="heading-forgetting-the-compatibility-bridge-when-needed">Forgetting the Compatibility Bridge When Needed</h3> <p>If you migrate your app's imports but don't add the <code>MaterialUiCompatibilityBridge</code>, and one of your dependencies still uses the old bundled Material, you may encounter runtime errors where widgets can't find their inherited theme data because they are looking in the wrong context. The symptom is a null theme or a "Could not find an ancestor of type MaterialLocalizations" error. The fix is always to add the bridge.</p> <h3 id="heading-running-pub-get-after-dart-fix-without-adding-the-packages-first">Running pub get After dart fix Without Adding the Packages First</h3> <pre><code class="language-bash"># WRONG order dart fix --apply --code=migrate_design_widgets # If pubspec.yaml was not updated, analysis errors remain # CORRECT order if the tool fails to update pubspec.yaml flutter pub add material_ui flutter pub add cupertino_ui dart fix --apply </code></pre> <p>The <code>dart fix</code> command needs the packages to be resolvable in your project for the import updates to validate correctly. If you run <code>dart fix</code> before the packages are in <code>pubspec.yaml</code>, it may update the import strings but leave you with unresolvable imports that the analyzer flags as errors.</p> <h3 id="heading-not-bumping-the-major-version-when-migrating-a-package">Not Bumping the Major Version When Migrating a Package</h3> <p>If you maintain a package and migrate it to <code>material_ui</code> without bumping the major version, consumers of your package who haven't yet added <code>material_ui</code> to their <code>pubspec.yaml</code> will get a dependency resolution failure when they update your package.</p> <p>Always bump the major version when your package adds a new external dependency, which is what switching from the bundled SDK library to an explicit package dependency represents.</p> <h3 id="heading-expecting-widgets-to-behave-differently-after-migration">Expecting Widgets to Behave Differently After Migration</h3> <p>Some developers expect the migration to Material 3 Expressive or other Material Design updates to happen as part of this migration. It does not. <code>material_ui</code> 1.0 is a faithful copy of <code>package:flutter/material.dart</code> at the point of the freeze. It's the same widgets with the same behavior at the same visual style. The decoupling is an architectural change, not a visual redesign. Future visual improvements from Material 3 Expressive will come in subsequent weekly releases of <code>material_ui</code> after 1.0.</p> <h2 id="heading-conclusion">Conclusion</h2> <p>The decoupling of Material and Cupertino from the Flutter SDK core is one of the most significant architectural changes Flutter has made since its initial release. What was a vision described in the earlier article <a href="https://www.freecodecamp.org/news/decoupling-material-and-cupertino-in-flutter/">Decoupling Material and Cupertino in Flutter</a> is now fully realized and ready for production adoption in Flutter 3.47.</p> <p>The migration path the Flutter team has built is as smooth as a breaking architectural change can be. The automated tool handles the import updates. The compatibility bridge handles the ecosystem gap. The API surface is frozen identically so no widget code changes. The localization setup gets simpler. And the payoff is immediate: weekly updates to your design system, independent of the quarterly SDK release cycle.</p> <p>The deprecation clock started with this release. November 2026 is when the old imports become formally deprecated. That's a comfortable runway for any team to complete the migration, but it's not a reason to wait. Every week you delay is a week of weekly Material updates you aren't getting.</p> <p>The three practical steps to take right now: run <code>flutter upgrade</code> to get Flutter 3.47, run <code>dart fix --apply --code=migrate_design_widgets</code> to migrate your imports, and run <code>flutter analyze</code> to verify the result. For most projects, those three commands are the entire migration. Add the compatibility bridge if your dependencies need it, and remove it as they migrate.</p> <p>Flutter 3.47 is a milestone. The ecosystem the decoupling unlocks, faster iteration, easier contributions, a style-neutral core, and independent design system versioning, is what makes Flutter genuinely modular by design. This is worth migrating to now.</p> <h2 id="heading-references">References</h2> <ul> <li><p><a href="https://flutter.dev/blog/whats-new-in-flutter-3-47">What's New in Flutter 3.47</a>: The official Flutter blog post announcing standalone UI packages, Impeller on desktop, widget previews going stable, and every other change in this release.</p> </li> <li><p><a href="https://docs.flutter.dev/release/breaking-changes">Flutter Breaking Changes Page</a>: The authoritative list of breaking changes in each Flutter release, including the decoupling migration details.</p> </li> <li><p><a href="https://pub.dev/packages/material_ui">material_ui on pub.dev</a>: The official standalone Material Design widget library for Flutter, published by flutter.dev, the replacement for <code>package:flutter/material.dart</code>.</p> </li> <li><p><a href="https://pub.dev/packages/cupertino_ui">cupertino_ui on pub.dev</a>: The official standalone Cupertino widget library for Flutter, the replacement for <code>package:flutter/cupertino.dart</code>.</p> </li> <li><p><a href="https://github.com/flutter/packages/tree/main/packages/material_ui">material_ui GitHub Repository</a>: Source code, issue tracking, and contribution guide for the standalone Material package.</p> </li> <li><p><a href="https://www.freecodecamp.org/news/decoupling-material-and-cupertino-in-flutter/">Decoupling Material and Cupertino in Flutter</a>: My earlier freeCodeCamp article explaining the motivation, design decisions, and preview state of the decoupling initiative before Flutter 3.47 completed it.</p> </li> <li><p><a href="https://github.com/orgs/flutter/projects/220">Decoupling GitHub Project</a>: The public GitHub project board tracking the decoupling work, showing what has been completed and what's still in progress.</p> </li> <li><p><a href="https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-plugin-authors">Swift Package Manager Migration Guide for Plugin Authors</a>: For plugin authors who also need to migrate to Swift Package Manager as part of the Xcode 27 transition.</p> </li> <li><p><a href="https://docs.flutter.dev/perf/impeller">Impeller Rendering Engine Documentation</a>: Complete documentation for Impeller, now the default renderer on all platforms, including how to opt out temporarily and how to file rendering bugs.</p> </li> </ul>

How to apply

  1. 1 Read the full details above and confirm you meet the eligibility criteria.
  2. 2 Prepare your documents — an updated CV, and any cover letter, proposal or certificates required.
  3. 3 Click Apply on official site to complete your application on Atuoha Anthony’s official page.
  4. 4 Submit as early as possible — many close once filled.
Apply on official site

Sourced from freecodecamp. Always verify details on the official website. Opportunihub never charges you to apply.

Frequently asked questions

How do I apply for How to Work with Material and Cupertino Decoupling in Flutter [Full Handbook]?

Review the full details and eligibility on this page, prepare your documents, then use the “Apply on official site” button to complete your application on Atuoha Anthony’s official page.

Is this opportunity remote or location-based?

This opportunity is remote-friendly and open to applicants who can work from anywhere.

Is How to Work with Material and Cupertino Decoupling in Flutter [Full Handbook] free to apply for?

Opportunihub lists this Course for free. Legitimate Courses do not ask for payment to apply — never pay a fee to submit an application.