Flutter 3.50の変更点まとめ — iOSのタッチ遅延を1フレーム短縮、Android Intentの検証強化

はじめに

2026年11月11日、Flutter 3.50 と Dart 3.14 がリリースされました。188人(うち初参加69人)による1,200以上のコミットを含む大型リリースです。

公式ブログの冒頭で強調されているのは、ネイティブ連携(direct native interop)の stable 化です。ただ、r/FlutterDev で反応が集まったのは、本文の iOS セクションにある改善でした。タッチしてから画面が反応するまでの遅延が縮み、「告知が控えめすぎる」という声が出たほどです。

この記事では、iOS のタッチ遅延がなぜ起きていたのか、Android のリリースビルドで何が動かなくなるのか、既存アプリで確認すべき移行作業は何かを整理します。

Flutter 3.50 の主な変更点

公式ブログで挙げられている変更のうち、実務に影響が大きいものは次のとおりです。

分野変更
iOSタッチ後のフレーム生成スケジュールを改修。入力遅延が 60Hz 端末で 16ms、120Hz 端末で 8ms 短縮
Androidリリースビルドで、外部から渡された Intent extras の route や dart_entrypoint などを検証し、通らなければ無視
ネイティブ連携Objective-C 向け FFIgen と Java/Kotlin 向け JNIgen が stable。Pigeon の新しいネイティブ連携モードはプレビュー
デザインpackage:flutter/material.dart と cupertino.dart を正式に非推奨化。新規プロジェクトは package:material_ui を使う
WebChrome / Edge 137 以降は既定で Wasm。Safari 26.2 以降と Firefox 147 以降も flutter_bootstrap.js の設定で Wasm を有効にできる。dart:html などの旧 JS 連携を非推奨化
ツールCocoaPods の trunk が2026年12月に読み取り専用になるため、Swift Package Manager を無効にすると毎回警告が出る

同時に出た Dart 3.14 には新しい言語構文はありません。代わりに、SDK 制約を1マイナーずつ上げながら dart fix を当てる dart migrate コマンドが追加されました。pub は ETag によるキャッシュに対応し、依存が多いプロジェクトでは pub get の通信量が減ります。

iOS のタッチ遅延 — 「1 vsync 待ち」をやめた

何が遅かったのか

Flutter が iOS でネイティブより1フレーム遅れて反応する問題は、2022年8月に #110431「Flutter adds additional one frame touch latency compared to native」として報告されていました。ネイティブアプリと並べると、スクロールやドラッグで指に対する追従がわずかに遅れて見える現象です。

原因は、タッチイベントを次の vsync(画面更新の区切り)まで待たせる仕組みにありました。2026年8月に立った #191199 では、iPhone 6s から iPhone 17 Pro まで実機で計測し、約99%のタッチが1 vsync 遅れて処理されていたことが示されています。遅らせていたのは SmoothPointerDataDispatcher などの、タッチの間隔を均すための仕組みです。均すこと自体には意味がありましたが、その代わりに必ず1フレーム分の遅延が乗っていました。

どう直したのか

3.50 では、Matej Knopp 氏によりタッチ後のフレーム生成スケジュールが作り直されました。公式ブログによると、タッチイベントを次の vsync まで待たせずにすぐフレームワークへ渡し、同じ画面更新の中でフレームを組み立てるようになっています。#191199 は PR #191444「Cleanup VSync infrastructure and improve iOS touch to present latency」で完了しました。

短縮幅は1フレーム分で、60Hz なら約16ms、ProMotion の 120Hz なら約8ms です。数字は小さく見えますが、指で直接触る操作では1フレームの差がはっきり体感に出ます。60fps と 120fps ほど違うという Reddit の感想も、1フレーム遅れがなくなったことと合っています。なお、公式ブログは今後のリリースで入力遅延をさらに縮める作業が進んでいると書いており、#110431 自体はこの記事の執筆時点でまだ open のままです。

Android — リリースビルドで Intent extras が無視される

何を防ぐのか

こちらは既存アプリが動かなくなる可能性がある変更です。Flutter の FlutterActivity と FlutterFragmentActivity は、Intent extras に入った次の値を読んで起動方法を変えられました。

  • route(初期ルート)
  • dart_entrypoint / dart_entrypoint_args(実行する Dart の関数と引数)
  • cached_engine_id / cached_engine_group_id(キャッシュ済みエンジンの再利用)

公式ブログは、これまでの Android アプリが「端末にインストールされた任意のアプリから」これらの設定を受け付けていたことを脆弱性とし、3.50 のリリースビルドで塞いだと説明しています。筆者の補足として具体的に考えると、ログイン後にしか見せない画面へ外部から直接遷移させる、意図しないエントリポイントを実行させる、認証済みのままメモリに残っているエンジンを再利用させる、といった悪用が考えられます。

3.50 のリリースビルドでは、送信元を検証してから extras を読みます。非公開の Activity(android:exported="false")への起動は信頼されます。Android 14 以降は起動元の UID がアプリ自身と一致するかを確かめ、Android 13 以前は呼び出し元のパッケージ名を確かめます(startActivityForResult() か startActivityIfNeeded() で起動したときだけ取得できる)。ACTION_VIEW のディープリンクは、マニフェストの <intent-filter> に一致する必要があります。検証に通らなければ extras は無視され、マニフェストの設定か既定値(ルートは /、エントリポイントは main)で起動します。デバッグ・プロファイルビルドでは検証が省略され、検証に失敗する場合は警告ログが出ます。

あわせて、--dart-flags などのエンジンフラグも、リリースビルドでは Intent 経由で受け付けなくなりました(警告ログを出して破棄)。flutter run --release --use-application-binary にフラグを付けた場合は CLI がエラーで止まります。どちらの変更にも、リリースビルドで無効にするオプトアウトは用意されていません。

影響を受けるケースと対応

影響を受けるのは、ネイティブ側から extras でルートを渡して Flutter 画面を開いているアプリです。ネイティブアプリに Flutter を部分的に組み込んでいる add-to-app 構成で、よく使われてきた書き方です。対応は公式ドキュメントに沿って次のいずれかを選びます。

  1. 外部から開く必要がない Activity は android:exported="false" にする(ランチャー Activity は exported のまま)
  2. 外部からの遷移は extras ではなく、<intent-filter> を宣言したディープリンクにする
  3. 固定のルートやエントリポイントはマニフェストの io.flutter.InitialRoute / io.flutter.Entrypoint に書く
  4. 動的に決めたい場合は Activity をサブクラス化し、getDartEntrypointFunctionName() や getDartEntrypointArgs() をオーバーライドする
  5. Android 13 以前で同じアプリ内から起動する場合は、startActivity() ではなく startActivityForResult() を使う(呼び出し元パッケージを取得できるようにするため)

ドキュメントには明記されていませんが、検証の規則から考えると、FlutterActivity.withNewEngine().initialRoute(...) で作った Intent も対象になります。このビルダーはルートを route extra に入れるためです。同じアプリ内からの起動でも、対象 Activity が exported で、Android 13 以前の端末で startActivity() を使っていると検証に通らず、ルートが / に戻ります。Android 14 以降や、非公開の Activity なら影響はありません。

注意したいのは、デバッグビルドでは検証が省略される点です。開発中は問題なく動き、リリースビルドで初めて「初期画面が / になる」という形で発覚します。3.50 に上げたら、リリースビルドで Flutter 画面への遷移を一通り確認しておくと安全です。CI でビルド済みのリリース APK にフラグを渡して計測していた場合も、プロファイルビルドに切り替える必要があります。

エンジニアへの影響 — 移行の優先順位

既存プロジェクトを 3.50 に上げるときは、次の順で確認すると手戻りが少なくなります。

  • Android の Intent 検証: リリースビルドでだけ挙動が変わり、開発中に気づけないので最初に確認する。ネイティブ側で EXTRA_INITIAL_ROUTE や withNewEngine().initialRoute を使っている箇所を洗い出し、対象 Activity の exported 設定と、サポートする Android の最低バージョンをあわせて確認する
  • iOS のビルド環境: CocoaPods から Swift Package Manager への移行が済んでいないなら、12月の trunk 読み取り専用化の前に着手する。Intel Mac でビルドしている場合も、Flutter はすでに Intel Mac でのテストをやめており、将来 Intel Mac のサポートが外れた時点で iOS / macOS / Android のビルドに Apple Silicon が必須になると書かれている
  • Material の非推奨化: 3.47 で material_ui 1.0 が出た流れの続きで、今回は警告が出るだけ。dart fix --apply --code=migrate_design_widgets で import を一括で書き換えられる
  • ネイティブ連携: プラットフォームチャネルで書いた既存コードはそのまま動く。FFIgen / JNIgen を使うと、プラットフォームチャネルのメッセージ受け渡しを経由せずにネイティブ API を直接呼べる。公式ブログによると、同期呼び出しやバックグラウンド isolate からの呼び出しもできる。新しく書く連携から検討するとよい

iOS のタッチ遅延の改善は、アップグレードするだけで得られます。ネイティブアプリを Flutter に置き換えるプロジェクトでは、「触ったときの感触がネイティブと違う」という指摘が出やすいため、3.50 に上げる理由として説明しやすい改善です。

一方、メジャー相当の変更が多いリリースなので、r/FlutterDev でも「x.x.1 のパッチ版を待ってから上げる」という意見が出ています。本番アプリは Android のリリースビルドの確認を済ませ、最初のパッチリリースを待ってから移行するのが堅実です。

まとめ

  • Flutter 3.50 と Dart 3.14 が2026年11月11日にリリースされた
  • iOS では、タッチを次の vsync まで待たせる仕組みをやめ、入力遅延が1フレーム分(60Hz で16ms、120Hz で8ms)縮んだ
  • Android のリリースビルドでは、外部から渡された route や dart_entrypoint などの Intent extras が検証され、通らなければ無視される。デバッグビルドでは気づけないため、リリースビルドで確認が必要
  • FFIgen(Objective-C)と JNIgen が stable になり、Material / Cupertino の旧ライブラリは正式に非推奨になった

公式ブログはネイティブ連携の専用記事を予告しています。Pigeon の新モードはまだプレビューなので、既存のプラットフォームチャネルをどこまで置き換えられるかは、その記事と今後のリリースで確かめたいところです。

ソース