Flutter 3.50: what’s new and what to do when you upgrade
Egor Dolgov · 12.11.2026Flutter 3.50 came out on November 11 together with Dart 3.14. In three minutes: what changed and what to check in your project.
Flutter 3.50 came out on November 11, 2026, together with Dart 3.14. This note covers only what affects your code and your build: what may break, what to update, and which new features are worth using. The full list of changes is in the announcement and the release notes .
Contents:
- Before you upgrade
- material.dart is deprecated: moving to material_ui
- Android: an older project may stop building
- Android: a release build ignores the route from an Intent
- iOS and macOS: CocoaPods and Intel Macs
- Web: Wasm in Safari and Firefox, file hashes, old JS interop
- What breaks in code
- New in code
- Dart 3.14
- Briefly about the rest
- Summary
1. Before you upgrade
Upgrade Flutter with the
flutter upgrade
command. If you keep several Flutter versions with FVM, install 3.50 next to the old one — that makes it easy to go back if the project doesn’t build:
FVM: several Flutter versions
.
The sections below go in order of importance: first what can stop your project from building, then the new features.
2. material.dart is deprecated: moving to material_ui
The Material and Cupertino widgets have moved out of Flutter into separate packages,
material_ui
and
cupertino_ui
. Since 3.50 the old libraries are
officially deprecated
: the line
import 'package:flutter/material.dart';
still works, but
flutter analyze
marks it as deprecated (
deprecated_member_use
).
You can migrate a project with one command in the project folder:
dart fix --apply --code=migrate_design_widgets
It replaces the imports. If the package didn’t appear in
pubspec.yaml
, add it yourself and run
dart fix
once more:
flutter pub add material_ui
For Cupertino, do the same with
cupertino_ui
. The import after the migration:
import 'package:material_ui/material_ui.dart';
-
In 3.50,
flutter createmakes new projects withmaterial_uifrom the start. -
Widget translations (
flutter_localizations) have moved into the package too: instead of a list of delegates,localizationsDelegates: GlobalMaterialLocalizations.delegatesfrommaterial_uiis enough. -
Latest versions on the date of checking:
material_ui1.11.0,cupertino_ui1.1.6. Both require Flutter 3.47 or newer.
If one of your dependencies still uses the old
material.dart
, its widgets won’t see the theme and translations from
material_ui
. Until the package is updated, there is a
bridge, `MaterialUiCompatibilityBridge`
: you wrap the app with it in
MaterialApp.builder
. The bridge is temporary — it is itself marked as deprecated — and it won’t help if the package accepts or returns old Material types in its API.
3. Android: an older project may stop building
Flutter 3.50 checks the Android tool versions during the build and stops the build if they are below the minimum:
- Android Gradle Plugin (AGP) — 9.1.1;
- Gradle — 9.3.1;
- Java — 17;
- Kotlin Gradle Plugin — 2.4.10 (checked only if the project applies it itself instead of using the Kotlin built into AGP).
In Flutter 3.47 the minimums were AGP 8.11.1 and Gradle 8.14.0, so projects on AGP 8 have to move to version 9.
This is the error you get if the project uses AGP 8.11.1:
Error: Your project's Android Gradle Plugin version (Android Gradle Plugin version 8.11.1) is lower than Flutter's minimum supported version of Android Gradle Plugin version 9.1.1. Please upgrade your Android Gradle Plugin version.
-
The AGP version is in
android/settings.gradle.kts(orsettings.gradle), in thepluginsblock, on thecom.android.applicationplugin. In older projects without that block, it’s inandroid/build.gradle, on the lineclasspath 'com.android.tools.build:gradle:<version>'. -
The Kotlin Gradle Plugin version is in the same
pluginsblock, on theorg.jetbrains.kotlin.androidplugin. -
The Gradle version is in
android/gradle/wrapper/gradle-wrapper.properties, on thedistributionUrlline. -
The
--android-skip-build-dependency-validationflag turns the check off, but it doesn’t guarantee that the project builds with the old tools.
A new project from
flutter create
in 3.50 gets AGP 9.3.1, Gradle 9.5.0, Kotlin Gradle Plugin 2.4.20 and Java 17. An easy way to find the right values for your project is to create an empty project next to it and compare these two files. The Flutter docs have no separate guide for moving an existing project to AGP 9. If, after the upgrade,
android/gradle.properties
gets the lines
android.builtInKotlin=false
and
android.newDsl=false
, Flutter added them itself: this keeps an older project building without the built-in Kotlin. How to move to it — the
migration page
.
4. Android: a release build ignores the route from an Intent
If another app opened yours with an
Intent
carrying a route (
EXTRA_INITIAL_ROUTE
) or an entrypoint, this
no longer works
in a release build: Flutter accepts that data only from the app itself. In debug and profile the check doesn’t get in the way, but if an
Intent
fails it, a warning is written to the log — so you can spot the affected places before release.
What to do: open the screen you need through ordinary
deep links
—
Intent.ACTION_VIEW
with an address declared in an
<intent-filter>
in the manifest.
A release build also
no longer reads
engine flags (
--dart-flags
) from an
Intent
. For performance measurements, use
flutter run --profile
.
5. iOS and macOS: CocoaPods and Intel Macs
- CocoaPods. On December 2, 2026, the CocoaPods repository stops accepting new library versions ; builds that work today won’t break. Since 3.44, Flutter uses Swift Package Manager by default. If you turned it off, Flutter 3.50 warns you when building for iOS and macOS: “Enabling Swift Package Manager will be required in a future version of Flutter”. Turn it back on:
flutter config --enable-swift-package-manager
If SwiftPM is turned off in the project’s
pubspec.yaml
(
enable-swift-package-manager: false
under
flutter: config:
), remove that line — it
takes precedence over the global setting
. For dependencies that don’t support SwiftPM yet, Flutter still uses CocoaPods.
-
Intel Macs.
Flutter is
phasing out support
for Intel-based Macs: the first time you run a
fluttercommand on such a computer, 3.50 warns once that a future version will require an Apple Silicon Mac. If your macOS app isn’t needed by users of Intel Macs, release and profile builds can target Apple Silicon only:flutter config --enable-macos-arm64-only.
6. Web: Wasm in Safari and Firefox, file hashes, old JS interop
Wasm in Safari and Firefox.
A
flutter build web --wasm
build
can now run
in Safari 26.2+ and Firefox 147+, but by default these browsers get the JavaScript version. Chrome and Edge (Chromium 137+) run Wasm by default. To turn Wasm on for Safari and Firefox, create a
flutter_bootstrap.js
file in the project’s
web
folder — Flutter will use it instead of the default one (
more details
):
{{flutter_js}}
{{flutter_build_config}}
_flutter.loader.load({
config: {
wasmAllowList: { blink: true, webkit: true, gecko: true },
},
});
Hashes in file names.
The
--web-content-hash
flag (
flutter build web --web-content-hash
) adds a content hash to the names of the build files — so that after you deploy a new version, the browser doesn’t take old files from its cache.
index.html
,
flutter_bootstrap.js
and
flutter.js
get no hash: the
docs recommend
serving them with
Cache-Control: no-cache
, and the hashed files with
public, max-age=31536000, immutable
. The flag doesn’t work with deferred imports (
deferred as
) yet.
Old JS interop.
dart:html
,
dart:js
,
dart:js_util
and the
js
package are
deprecated
and will be removed from Dart; they don’t work with Wasm already. The replacements are
package:web
and
dart:js_interop
—
migration guide
. To find old imports in the project and its dependencies:
flutter build web --no-deprecated-js-interop
— the build fails on such imports.
7. What breaks in code
ignoringSemantics
is removed
from
IgnorePointer
,
AbsorbPointer
and
SliverIgnorePointer
. To hide part of the screen from screen readers, wrap it in
ExcludeSemantics
(
migration
):
import 'package:material_ui/material_ui.dart';
class LockedForm extends StatelessWidget {
const LockedForm({super.key});
@override
Widget build(BuildContext context) {
// Before: IgnorePointer(ignoringSemantics: true, child: ...)
return const ExcludeSemantics(
child: IgnorePointer(
child: TextField(),
),
);
}
}
Soft hyphens are now visible.
When a line breaks at a soft hyphen (U+00AD), Flutter
draws a hyphen
on Android, iOS and desktop. To get the old look back, use
hyphens: Hyphens.hidden
:
import 'package:material_ui/material_ui.dart';
// \u00AD is a soft hyphen: a place where the word may break.
const longWord = 'Interna\u00ADtionalization';
class Hyphenated extends StatelessWidget {
const Hyphenated({super.key});
@override
Widget build(BuildContext context) {
return const Column(
children: [
// Since 3.50: a hyphen is drawn at the break.
SizedBox(width: 120, child: Text(longWord)),
// As before: a break without a hyphen.
SizedBox(width: 120, child: Text(longWord, hyphens: Hyphens.hidden)),
],
);
}
}
Two more changes affect only a few apps:
-
A
WidgetSpaninside right-to-left text (Arabic, Hebrew) now sits in the right place . If you used to shift such widgets by hand, remove that workaround. -
On the web with
viewport-fit=coverinweb/index.html,SafeAreaandMediaQuery.paddingOfnow take the screen notch into account . If your UI moved under the notch, wrap it inSafeArea— or removeviewport-fit=coverfrom the<meta name="viewport">tag if you don’t need edge-to-edge display.
8. New in code
All of these APIs are in Flutter itself; you can reach them through the
material_ui
import too.
ListView.separated
row heights in advance.
itemExtentBuilder
tells the list each item’s height in advance — the list scrolls faster to far positions. The indexes are interleaved: even ones are rows, odd ones are separators. For 100 rows there are 199 items; for an index beyond that, return
null
:
import 'package:material_ui/material_ui.dart';
class ContactList extends StatelessWidget {
const ContactList({super.key});
@override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: 100,
itemBuilder: (context, index) => ListTile(title: Text('Contact $index')),
separatorBuilder: (context, index) => const Divider(height: 1),
itemExtentBuilder: (index, dimensions) {
if (index >= 199) return null;
return index.isEven ? 56 : 1;
},
);
}
}
The “Reduce Motion” setting.
MediaQuery.reduceMotionOf(context)
tells you whether the user turned on Reduce Motion in the iOS settings. Flutter doesn’t turn off its own animations because of it — that’s up to you:
import 'package:material_ui/material_ui.dart';
class Pill extends StatelessWidget {
const Pill({super.key, required this.selected});
final bool selected;
@override
Widget build(BuildContext context) {
final reduceMotion = MediaQuery.reduceMotionOf(context);
return AnimatedContainer(
duration: reduceMotion ? Duration.zero : const Duration(milliseconds: 300),
width: selected ? 120 : 60,
height: 40,
color: selected ? Colors.blue : Colors.grey,
);
}
}
Your own backdrop under a dialog.
barrierBuilder
lets you wrap the dimmed barrier under a dialog — for example, to blur the screen. In 3.50 it exists on
showGeneralDialog
;
showDialog
from
material_ui
1.11.0 doesn’t have it yet, although the announcement and the release notes say otherwise:
import 'dart:ui';
import 'package:material_ui/material_ui.dart';
void showBlurDialog(BuildContext context) {
showGeneralDialog<void>(
context: context,
barrierDismissible: true,
barrierLabel: 'Close',
barrierBuilder: (context, details, barrier) => BackdropFilter(
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
child: barrier,
),
pageBuilder: (context, animation, secondaryAnimation) => const Center(
child: Card(
child: Padding(padding: EdgeInsets.all(24), child: Text('Hello!')),
),
),
);
}
A numeric keyboard for a PIN.
TextInputType.numberWithOptions(password: true)
opens a numeric password keyboard on Android. On iOS, the web and desktop the parameter changes nothing:
import 'package:material_ui/material_ui.dart';
class PinField extends StatelessWidget {
const PinField({super.key});
@override
Widget build(BuildContext context) {
return const TextField(
keyboardType: TextInputType.numberWithOptions(password: true),
obscureText: true,
decoration: InputDecoration(labelText: 'PIN code'),
);
}
}
A placeholder while an image loads.
DecorationImage
now has
placeholder
— an image shown until the main one has loaded. The
assets/avatar.png
picture has to be added to the project and listed in
pubspec.yaml
:
import 'package:material_ui/material_ui.dart';
class Avatar extends StatelessWidget {
const Avatar({super.key, required this.url});
final String url;
@override
Widget build(BuildContext context) {
return Container(
width: 64,
height: 64,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(url),
placeholder: const AssetImage('assets/avatar.png'),
fit: BoxFit.cover,
),
),
);
}
}
A code from an email.
AutofillHints.emailOTPCode
tells Android autofill that the field expects a one-time code from an email.
A disabled
DropdownButtonFormField
.
In
material_ui
it has a new
enabled
parameter, and
onChanged
is no longer required. Old code with
onChanged: null
keeps working
. Mind the difference:
enabled: false
turns off the field’s own validation (
autovalidateMode
); if you need errors on a disabled field via
AutovalidateMode.always
, keep
onChanged: null
.
9. Dart 3.14
Dart 3.14 has no new language features . What’s useful:
-
A fix in type analysis.
A subpattern in a list, map or record pattern no longer promotes the type of the whole matched property. The fix applies to all code regardless of the language version in
pubspec.yaml, so after the upgrade the analyzer may report new errors in code withswitchandcasepatterns. -
dart migrateraises the SDK version inpubspec.yamland updates the code for it. It doesn’t run without a flag: to preview the changes —dart migrate --dry-run, to write them —dart migrate --apply. One run raises the version by one minor step (for example, from^3.12.0to^3.13.0).
10. Briefly about the rest
- Touch response on iOS. According to the announcement, on iOS the picture after a touch appears one frame earlier: 16 ms at 60 Hz and 8 ms at 120 Hz. Nothing to do.
- Hybrid Composition++ — an experimental (since Flutter 3.44) way to show native Android views (platform views) inside Flutter; 3.50 improves it. Off by default; requires Android 14 (API 34) and Impeller on Vulkan. How to turn it on .
- For plugin authors: it’s time to replace the Kotlin Gradle Plugin with the Kotlin built into AGP — guide .
11. Summary
-
Move your imports to
material_ui:dart fix --apply --code=migrate_design_widgets. - Android: AGP at least 9.1.1, Gradle at least 9.3.1, Java 17 — otherwise the build stops.
- iOS: keep Swift Package Manager on — from December 2, 2026, CocoaPods gets no new library versions.
-
Check your code for the removed
ignoringSemanticsand for soft hyphens; for web projects, replacedart:htmlandpackage:js.
Send questions and suggestions about the article to contact@flutter.su.