Start Debugging

Material- und Cupertino-Importe in Flutter auf die Pakete material_ui und cupertino_ui migrieren

Die vollständige Migration von package:flutter/material.dart und package:flutter/cupertino.dart auf material_ui 1.1.1 und cupertino_ui 1.0.2: was dart fix --code=migrate_design_widgets umschreibt, warum Widgets aus Drittpaketen plötzlich Ancestor-Lookups scheitern lassen, was MaterialUiCompatibilityBridge tatsächlich behebt und wie sich die Abhängigkeit zu flutter_localizations ändert.

Für eine App, deren einzige Material-Oberfläche ihr eigener Code ist, ist das eine Migration von einem Befehl und einem Nachmittag: flutter pub add material_ui, dann dart fix --apply --code=migrate_design_widgets, dann die Tests laufen lassen. Die Widget-APIs sind eine identische Kopie dessen, was im SDK lag, also rendert nichts anders und kein Golden sollte sich bewegen. Zeit kostet der Abhängigkeitsgraph. Jedes Paket, das noch package:flutter/material.dart importiert, zieht eine zweite, typinkompatible Kopie von Theme, Material und MaterialLocalizations in Ihr Programm, und dessen Widgets scheitern an Ancestor-Lookups in Ihrem migrierten Baum, bis Sie die App in MaterialUiCompatibilityBridge einwickeln. Diese Anleitung zielt auf den aktuellen Stable-Kanal, Flutter 3.47.2 mit Dart 3.13.2, plus material_ui 1.1.1 und cupertino_ui 1.0.2.

Die Uhr läuft hier mit. Die Bibliotheken im SDK sind bereits eingefroren, und die formale Deprecation ist für das Stable-Release im November 2026 geplant.

Warum das keine optionale Aufräumaktion ist

Was bricht

BereichÄnderungSchweregrad
Importepackage:flutter/material.dart wird package:material_ui/material_ui.dart; package:flutter/cupertino.dart wird package:cupertino_ui/cupertino_ui.darthoch, vollständig automatisierbar
TypidentitätDas Material des SDK und das Material aus material_ui sind zur Laufzeit verschiedene Typen, deshalb überschreiten Ancestor-Lookups die Grenze nichthoch, benötigt die Brücke
Lokalisierungs-DelegatesGlobalMaterialLocalizations und GlobalCupertinoLocalizations kommen aus den Paketen, nicht aus flutter_localizationsmittel
pubspec.yamlZwei neue direkte Abhängigkeiten; flutter_localizations ist keine direkte Abhängigkeit mehr, die Sie brauchenmittel
Generierter CodeAlles, was package:flutter/material.dart in eine .g.dart- oder .freezed.dart-Datei schreibt, muss nach dem Durchlauf über den Quellcode neu generiert werdenmittel
Veröffentlichte PaketeIhr eigenes Paket zu migrieren ist eine Breaking Change für Konsumenten und braucht daher einen Major-Versionssprungmittel
Widget-APIsKeine. Konstruktoren, Parameter und Rendering bleiben unverändertkeine

Diese letzte Zeile ist der ganze Grund, warum diese Migration machbar ist. material_ui 1.0.0 ist eine Kopie der mitgelieferten Bibliothek im Stand des Freeze vom April 2026, kein Redesign.

Checkliste vor dem Start

Migrationsschritte

  1. Fügen Sie die Pakete hinzu, bevor Sie einen einzigen Import anfassen. Die dart fix-Regel schreibt Import-Strings um; sie bearbeitet pubspec.yaml nicht. In der falschen Reihenfolge bekommen Sie eine Datei voller nicht auflösbarer Importe.

    # Flutter 3.47.2, Dart 3.13.2
    flutter pub add material_ui
    flutter pub add cupertino_ui

    Das löst heute zu material_ui: ^1.1.1 und cupertino_ui: ^1.0.2 auf. Ist Ihre App reines Material, erhalten Sie cupertino_ui trotzdem transitiv, denn material_ui hängt seit Release 1.0.1 von cupertino_ui: ^1.0.0 ab; geben Sie es aber explizit an, wenn Sie es direkt importieren. Prüfen mit flutter pub deps --style=compact | grep -E 'material_ui|cupertino_ui' und bestätigen, dass beide auflösen.

  2. Schreiben Sie die Importe mit dem mitgelieferten Fix um. Beide Pakete registrieren denselben Analyzer-Fix, ein Befehl erledigt also Material und Cupertino gemeinsam.

    dart fix --dry-run --code=migrate_design_widgets   # review first
    dart fix --apply  --code=migrate_design_widgets

    Das Ergebnis ist ein einzeiliger Diff pro Datei:

    // Before: Flutter 3.43 and earlier
    import 'package:flutter/material.dart';
    
    // After: material_ui 1.1.1
    import 'package:material_ui/material_ui.dart';

    Unterhalb der Import-Zeile ändert sich nichts. MaterialApp, Scaffold, ThemeData, Colors, showDialog und jeder andere Name werden unter demselben Identifier exportiert. Prüfen mit grep -rn "package:flutter/material.dart\|package:flutter/cupertino.dart" lib test, das nichts zurückgibt, danach flutter analyze.

  3. Richten Sie die Lokalisierungs-Delegates auf die Pakete aus. Die Delegates und die übersetzten Strings sind nach material_ui und cupertino_ui gewandert, und die Pakete bieten einen Sammel-Getter, der Ihnen das Auflisten von drei Delegates erspart.

    // Before: flutter_localizations, Flutter 3.43
    import 'package:flutter_localizations/flutter_localizations.dart';
    
    localizationsDelegates: const <LocalizationsDelegate<Object>>[
      GlobalMaterialLocalizations.delegate,
      GlobalCupertinoLocalizations.delegate,
      GlobalWidgetsLocalizations.delegate,
    ],
    // After: material_ui 1.1.1
    import 'package:material_ui/material_ui.dart';
    
    localizationsDelegates: GlobalMaterialLocalizations.delegates,

    GlobalMaterialLocalizations.delegates enthält die Cupertino- und Widgets-Delegates bereits. Wenn Sie zusätzlich gen-l10n nutzen, bleibt Ihr generiertes AppLocalizations.delegate unberührt und wird wie bisher an diese Liste angehängt. flutter_localizations können Sie nun aus Ihren eigenen dependencies entfernen, es bleibt aber in pubspec.lock: cupertino_ui 1.0.2 hängt weiterhin davon ab, neben collection: ^1.19.1 und intl: ^0.20.2. Prüfen, indem Sie mit einer nicht-englischen Locale starten und einen eingebauten String kontrollieren, zum Beispiel ein TextField lang drücken und bestätigen, dass die Einfügen-Option übersetzt ist.

  4. Überbrücken Sie die Abhängigkeiten, die nicht migriert sind. Das ist der Schritt, den man überspringt und anschließend eine Stunde debuggt. Auf App-Ebene mit MaterialApp.builder einwickeln:

    // material_ui 1.1.1
    MaterialApp(
      theme: ThemeData(useMaterial3: true),
      builder: (BuildContext context, Widget? child) {
        return MaterialUiCompatibilityBridge(child: child!);
      },
      home: const HomeScreen(),
    )

    Die Cupertino-Seite ist symmetrisch:

    // cupertino_ui 1.0.2
    CupertinoApp(
      builder: (BuildContext context, Widget? child) {
        return CupertinoUiCompatibilityBridge(child: child!);
      },
      home: const HomeScreen(),
    )

    Sie können auch einen engeren Teilbaum einwickeln, wenn nur ein Screen alte Widgets einbettet; das hält die zusätzlichen Inherited Widgets aus dem restlichen Baum heraus. Prüfen, indem Sie jeden Screen aufrufen, der ein Widget aus einem Drittpaket enthält. Die Brücke ist temporäres Baugerüst: Löschen Sie sie, sobald flutter pub outdated nichts mehr mit den alten Importen zeigt.

  5. Generieren Sie alles neu, was ein Codegenerator geschrieben hat. dart fix sieht Ihren Quellcode, nicht die Templates, die ihn erzeugt haben. Lassen Sie den Generator nach Schritt 2 erneut laufen, damit die erzeugten Dateien die SDK-Bibliothek nicht mehr importieren:

    dart run build_runner build --delete-conflicting-outputs

    Prüfen Sie danach die Reste, die dart fix nicht erreicht: export-Barrel-Dateien, die Material für Konsumenten re-exportieren, bedingte Importe, die pro Plattform eine Material-Implementierung wählen, und jedes eigene Generator-Template, in dem der Importpfad als String fest hinterlegt ist. Prüfen mit demselben grep aus Schritt 2, aber über das gesamte Repository statt nur lib und test.

  6. Wenn Sie ein Paket veröffentlichen, erhöhen Sie die Major-Version. Ein veröffentlichtes Paket auf material_ui umzustellen ändert, was Konsumenten in ihrer eigenen pubspec.yaml haben müssen. Das als Minor-Release auszuliefern bricht Apps stillschweigend: Ihr Widget-Baum mischt am Ende Quellen, ohne dass ein Compilerfehler darauf zeigt. Springen Sie auf die nächste Major-Version, notieren Sie die nötige material_ui-Constraint im Changelog, und halten Sie die vorige Major-Version auf einem Wartungsbranch, wenn Sie ältere Flutter-Versionen unterstützen. Prüfen mit dart pub publish --dry-run.

Verifikation

Rollback

Heute vollständig umkehrbar. Die Änderungen sind ein pubspec.yaml-Diff, eine Import-Zeile pro Datei, eine Delegates-Liste und ein optionales Brücken-Widget, ein git revert des Migrations-Commits bringt Sie also zurück auf die SDK-Bibliotheken, ohne Daten oder Build-Artefakte zurückzudrehen. Zwei Einschränkungen: Es gibt kein umgekehrtes dart fix, ein manuelles Rollback heißt also, jeden Import per Hand zurückzuschreiben, weshalb Schritt Null ein Branch ist. Und nach dem Stable-Release im November 2026 parkt ein Revert Sie auf formal deprecated APIs, die gelöscht werden; behandeln Sie Rollback daher als Mittel, ein Release freizubekommen, nicht als Entscheidung.

Fallstricke

“Could not find an ancestor of type MaterialLocalizations” aus Code, den Sie nicht geschrieben haben. Das ist das Typidentitätsproblem zur Laufzeit. Ein Widget, das gegen die SDK-Bibliothek kompiliert wurde, ruft MaterialLocalizations.of(context) auf, was den Baum nach dem Inherited Widget seines MaterialLocalizations-Typs durchsucht. Ihre MaterialApp aus material_ui hat einen anderen Typ mit gleichem Namen eingefügt, der Lookup schlägt fehl, und der Assert greift. Theme.of(context) scheitert genauso, mit “Could not find an ancestor of type Theme”. Die Brücke aus Schritt 4 existiert genau dafür, die alten Inherited Widgets neben den neuen einzufügen, damit beide Lookups auflösen. Sie ist kein Ersatz für ein fehlendes Scaffold: Kommt der Fehler aus Ihrem eigenen migrierten Code, haben Sie das gewöhnliche Problem aus no Material widget found in Flutter, und die Brücke hilft nicht.

Nicht auflösbarer Import direkt nach dem Fix. Sie haben dart fix vor flutter pub add ausgeführt. Paket hinzufügen, dann dart fix --apply --code=migrate_design_widgets erneut laufen lassen; die Regel ist idempotent.

Lassen Sie nicht beide Importe in einer Datei. package:flutter/material.dart und package:material_ui/material_ui.dart exportieren die gleichen Identifier, jede Datei mit beiden bekommt also Fehler wegen mehrdeutiger Importe bei Material, Theme, Colors und Co. Einen davon zu präfixen kompiliert, gibt Ihnen aber zwei Designsysteme in einer Datei, was schlimmer als der Fehler ist. Pro Datei eines wählen.

Freeze-Datum und Deprecation-Datum sind nicht dasselbe. Die Ankündigung des Code Freeze sagte, die SDK-Bibliotheken würden im Stable-Release nach 3.44 deprecated. Das hat sich verschoben: 3.47 erschien am 2026-08-12 ohne die Deprecation, und die Release Notes zu 3.47 setzen die formale Deprecation nun auf die November-Stable. Eingefroren seit April, deprecated im November, später gelöscht. Planen Sie gegen November, nicht gegen das, worüber Ihr Analyzer heute schweigt.

Asset-Manifeste können sich verschieben, auch wenn die Widgets es nicht tun. material_ui 1.1.0 hat das Shader-Asset ink_sparkle über die eigene pubspec.yaml bereitgestellt und den stretch_effect-Shader entfernt. Wenn Sie auf das Asset-Manifest testen oder unbenutzte Assets in einem Build-Schritt entfernen, ist das ein echter Diff zum Prüfen.

Migrieren Sie Importe und Flutter-Versionen in getrennten Commits. Wenn Sie im selben Durchgang SDK-Versionen springen, hat jede visuelle Regression zwei Kandidaten als Ursache. Erst das SDK-Upgrade landen, bestätigen, dass die App sauber ist, dann die Importe migrieren.

Verwandte Beiträge

Quellen

Comments

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

< Zurück