Flutterから Material が消えた日 — material_ui / cupertino_ui 分離の全体像と移行手順

はじめに

Flutter を書いたことがある人なら、ファイルの1行目に指が勝手に打つ文字列がある。import 'package:flutter/material.dart'; だ。この1行はほぼ「Flutter を始める」と同義で、10年近く誰も疑わずに書いてきた。

その1行が、非推奨になる。Flutter 3.47 で Material と Cupertino が SDK の外に出て、material_ui / cupertino_ui という pub.dev の独立パッケージとして 1.0 を迎えた。11月の Fall stable リリースで SDK 側の旧ライブラリは正式に非推奨化され、削除は 2027 年が予定されている。

r/FlutterDev で話題になったこの変更は、見出しだけ読むと「import パスが変わるだけ」に見える。だが実体は 2022 年から動いていた4年越しのアーキテクチャ再編で、狙いは import パスではなく Flutter の widgets 層そのものを作り直すことにあった。この記事では、何が起きたのか、なぜそうしたのか、そして11月までに何をすべきかを整理する。

何が起きたのか

変更の中身は具体的だ。

  • package:flutter/material.dart → package:material_ui/material_ui.dart
  • package:flutter/cupertino.dart → package:cupertino_ui/cupertino_ui.dart
  • どちらも pub.dev で flutter.dev の verified publisher として配布。9月1日時点で material_ui は 1.1.0 まで進んでいる
  • material_ui は内部で cupertino_ui に依存しているため、両方を明示 import する必要は多くのファイルで生じない

重要なのは、移動したのはデザインシステムのライブラリだけだという点だ。rendering.dart、services.dart、gestures.dart、そして widgets.dart は SDK に残る。レイアウト・描画・ジェスチャ・プラットフォームチャネルといった土台は動いていない。動いたのは、その上に乗っていた「見た目」の層である。

そして最大の実利は、リリースサイクルの分離だ。これまで Material のボタンに1つバグ修正が入るのを待つには、四半期ごとの Flutter SDK リリースを待ち、SDK 全体を上げる必要があった。分離後は、デザインライブラリだけを週次ペースで上げられる。SDK のバージョンを固定したまま最新の Material 3 コンポーネントを取り込む、という今まで不可能だった運用が成立する。

なぜ4年かかったのか — 3フェーズの設計

この計画は Issue #101479 として 2022年4月に立てられ、P1 として長く走ってきた。動機として挙げられていたのは、Material/Cupertino だけが特別扱いされている歪みだった。fluent_ui、macos_ui、yaru といった他のデザインシステムは pub.dev のパッケージとして自由に進化できるのに、Material と Cupertino だけがフレームワーク本体に埋め込まれ、SDK のリリース速度に縛られている。

計画は3フェーズに分かれていた。

  1. Reinforcement(補強) — デザインライブラリの中にしかなかった基礎ロジックを、core の widgets 層へ引き上げる
  2. Relocation(移設) — Material / Cupertino を独立パッケージ化する
  3. Removal(撤去) — SDK 側の旧ライブラリを非推奨化し、削除する

時間がかかったのは、ほぼ全部フェーズ1のせいだ。というのも、Material のウィジェットには「見た目」と「振る舞い」が分かちがたく混ざっていた。たとえばメニューの位置決めロジック、ラジオボタンのグループ選択状態の管理、ツールチップの表示タイミング制御——これらは Material 固有の意匠ではなく、どんなデザインシステムでも必要になる汎用ロジックなのに、material.dart の中にしか存在しなかった。

そこで Issue #97496 の下で、スタイルを持たないプリミティブが widgets 層に次々と追加された。RawMenuAnchor、RawMenuAnchorGroup、RawRadio、RawTooltip、開閉状態を扱う Expansible などだ。既存の RawAutocomplete / RawScrollbar / RawMagnifier / RawGestureDetector と同じ「Raw」系列である。

目標として掲げられていた基準がわかりやすい。Material を import せずに、ボタン・スライダー・スイッチを組み立てるのに必要なロジックが widgets 層に100%あること。この条件を満たして初めて、Material は「取り外せる部品」になる。逆に言えば、4年間の作業の本体は分離そのものではなく、分離を可能にする土台の整備だった。

移行は何をすればいいのか

実務側の手順は、拍子抜けするほど短い。

dart fix --apply --code=migrate_design_widgets

これで import の書き換えは自動で通る。「semantically breaking but mechanically automated(意味論的には破壊的だが、機械的には自動化されている)」という表現がこの移行の性格をよく表している。ただし自動化されないポイントが3つある。

1. pubspec への追加
初期のツールには pubspec.yaml の扱いに既知の不具合があり、flutter pub add material_ui cupertino_ui を手で叩く必要が出るケースが報告されている。dart fix を走らせる前に依存を入れておくのが安全だ。

2. ローカライゼーションのデリゲート
従来は GlobalMaterialLocalizations.delegate / GlobalWidgetsLocalizations.delegate / GlobalCupertinoLocalizations.delegate の3つを並べていたが、material_ui 版では GlobalMaterialLocalizations.delegates(複数形のゲッター) に集約され、Cupertino と Widgets のデリゲートを内包する。ここは import 書き換えだけでは通らず、日本語アプリなら確実に踏む。

3. 移行していない依存パッケージ
自分のコードを直しても、依存している third-party パッケージが古い package:flutter/material.dart を使っていれば型が噛み合わない。この橋渡し用に MaterialUiCompatibilityBridge が用意されていて、MaterialApp.builder 経由でアプリを包むことで段階的な移行ができる。

そしてパッケージを公開している側は事情が違う。利用者側に material_ui への明示的な依存を要求することになるため、これは major version bump 相当の破壊的変更として扱う必要がある。11月の非推奨化までに、自分が maintainer をしているパッケージがないか棚卸ししておきたい。

エンジニアへの影響

短期的に効くのは、SDK バージョンと UI ライブラリバージョンの切り離しだ。「Impeller や Wasm 周りの変更が怖くて SDK を上げられないが、Material のバグ修正だけは欲しい」という板挟みは、実務でよくある。分離後はこの2つを別々に判断できる。

中期的にもっと大きいのは、Material を使わない選択肢が現実的になったことだ。独自デザインシステムを持つプロダクトは、これまで Material をベースに上書きして戦うか、Raw ウィジェットが足りない部分を自前で書くかの二択だった。widgets 層に振る舞いのプリミティブが揃った今、package:flutter/widgets.dart だけを土台にデザインシステムを組む道が開ける。r/FlutterDev で「ヘッドレスアプリ」と呼ばれていたのはこの構図だ。Material を「デフォルトで付いてくるもの」ではなく「数あるデザインパッケージの1つ」として選び直せる、と言い換えてもいい。

一方で、エコシステム全体には移行期の摩擦が来る。pub.dev の膨大なウィジェット系パッケージが一斉に major bump する期間が発生し、しばらくは「A は移行済み、B は未対応」という組み合わせ問題に付き合うことになる。MaterialUiCompatibilityBridge はまさにこの期間のための道具であり、逆に言えばこの橋が必要な状況が当分続くという前提で計画を立てるべきだ。

なお、この移行を AI エージェントに丸投げする場合は注意が必要だという指摘も出ている。多くのモデルは学習カットオフの都合で split package 構成を知らず、存在しないパッケージ名や旧 import パスを自信満々に出してくる。dart fix のルール名や新しいデリゲートの仕様といった一次情報を、明示的にコンテキストへ渡す運用が要る。

まとめ

  • Flutter 3.47 で Material / Cupertino が material_ui / cupertino_ui として SDK から分離、1.0 に到達した
  • SDK 側の旧ライブラリは11月の stable で非推奨化、2027年に削除予定
  • 4年間の作業の本体は分離ではなく、RawMenuAnchor / RawRadio / RawTooltip などスタイル非依存プリミティブを widgets 層に揃える「Reinforcement」だった
  • 移行は dart fix --apply --code=migrate_design_widgets でほぼ自動。手当てが要るのは pubspec・ローカライゼーションのデリゲート・未移行の依存パッケージの3点
  • パッケージ公開者にとっては major version bump 相当の破壊的変更

「Material が消える」という言い方は、失うものの話に聞こえる。だが実際に起きたのは逆で、Material に依存しない土台が Flutter の中に初めて完成したということだ。今後1〜2年は移行の摩擦が続くが、その先にあるのは Material がデフォルトではなく選択肢になった Flutter である。

今日のその他のニュース

エラーログを AI に解析させるだけで感染する — エラーログをそのまま LLM に貼り付けて原因を聞く運用に、プロンプトインジェクションの経路があるという指摘が @IT で出ている。ログは攻撃者が内容を混入させうる外部入力であり、そこに書かれた文字列が AI への指示として解釈されうる。ログ監視から自動で LLM を呼ぶパイプラインを組んでいる場合、信頼境界の引き直しが要る。(@IT)

OpenAI が Mac mini / Mac Studio を数万台規模で調達 — Apple Silicon の統合メモリを推論基盤として大量に使う動きが報じられた。GPU 供給が逼迫するなか、コンシューマ機の統合メモリを推論用途に束ねる選択が、この規模で成立しはじめている。(GIGAZINE)

Supabase が realtime スキーマへの変更を全面ブロック — 8月の Developer Update で、realtime スキーマに対する CREATE / ALTER / DROP が権限エラーになるセキュリティ変更が入った。realtime.messages の RLS ポリシーは従来どおり動作するが、マイグレーションで realtime スキーマを触っている場合は影響を受ける。(Supabase)

ソース

※ r/FlutterDev の元スレッドは取得時にアクセスできず、デイリーダイジェストの要約と上記一次ソースをもとに構成した。