Published November 13, 2026
Flutter 3.50 Goes Stable: Direct Native Interop, Faster Wasm on Safari and Firefox, and an iOS Latency Win
Flutter 3.50 is stable, and it landed on November 11 alongside Dart 3.14. The release is the first since Xcode 27 and iOS 27 shipped, the first where direct native interoperability is production ready, and the first where a project created with flutter create imports package:material_ui instead of the bundled design library. The team frames the cycle as an investment in the framework's next decade, and the changelog matches that framing: most of the work is infrastructure that later features will sit on.
The cycle took 188 contributors, 69 of them first-timers, and more than 1,200 commits. Several headline performance fixes came from outside Google.
Native Interop Leaves Preview
FFIgen for Objective-C and JNIgen for Java and Kotlin are now stable, joining FFIgen's long-standing C support. Platform channels serialize arguments into a binary buffer, cross a bridge, deserialize on the native side, run the call, and reverse the process. Direct interop skips that: Dart calls native code through FFI or JNI, synchronously if you want, and from background isolates when you need it.
Pigeon, the contract-first generator most plugin authors already use, can now sit on top of both. Version 30 and later expose the mode through SwiftOptions(useFfi: true) and KotlinOptions(useJni: true), so the workflow is unchanged: define the API, run Pigeon, implement the generated interface in Swift or Kotlin. Pigeon then handles data conversion and native object lifetimes, the part raw bindings leave to you. The preview covers Android, iOS, and macOS; Windows and Linux keep platform channels, and both outputs can come from one Pigeon file. shared_preferences has already been migrated, which makes it the reference for converting an existing plugin.
WebAssembly Reaches Safari and Firefox
Web is where the numbers are easiest to measure. On Safari 26.2 and later for macOS and iOS, and on Firefox 147 and later, a Wasm build delivers more than twice the frame rate of JavaScript on heavy widget benchmarks. Chromium browsers have defaulted to WebAssembly since Chrome and Edge 137. Apps opt in ahead of the default switch through the loader configuration in web/flutter_bootstrap.js, setting wasmAllowList entries for blink, webkit, and gecko.
Three smaller web changes matter for operations. The new --web-content-hash flag embeds content hashes into compiled JavaScript, WebAssembly, and asset filenames, then rewrites flutter_bootstrap.js to match. That lets an app cache hashed bundles for a year while keeping index.html and the bootstrap file on no-cache, which removes the classic stale-deploy failure. A second flag, --no-deprecated-js-interop, fails the build when the app or any dependency still imports dart:html, dart:js, dart:js_util, or package:js, all of which are deprecated in favor of dart:js_interop and package:web.
- Web: run the interop gate on every package now, since the legacy libraries are scheduled for removal
- Android: release builds ignore route, dart_entrypoint, and cached_engine_id Intent extras
- iOS: disabling Swift Package Manager warns on every CLI command as the CocoaPods fallback winds down
- Desktop: RegularWindow is renamed Window, and sizedToContent becomes shrinkWrap
A Frame of Latency Removed on iOS
The iOS change developers will feel rather than read is a rework of frame scheduling after touch events, contributed by community member Matej Knopp. A touch event used to wait for the next vsync before the frame started building. Now it reaches the framework immediately and the frame lands in the same refresh: 16 ms faster on 60 Hz devices and 8 ms faster on 120 Hz ProMotion displays, measured on hardware going back to the 2015 iPhone 6s.
Debugging on iOS 27 also stops requiring a detour through Xcode. Running on a newly paired iPhone no longer hangs on a white screen while the device is prepared, and flutter screenshot works on physical devices again for the first time since iOS 17. With the CocoaPods trunk going read-only in December 2026, Flutter warns on every command when Swift Package Manager is disabled, and Intel Mac hosts now get a warning too, with flutter config --enable-macos-arm64-only available for Apple Silicon-only builds.
Android gets the Intent hardening documented in the breaking change list, plus Hybrid Composition++ refinements: unbuffered motion event dispatch on AndroidViewSurface and a layout ordering fix in RenderAndroidView. HCPP stays opt-in on stable, is the beta default since 3.49.0-0.1.pre, and needs Android 14 with Impeller on Vulkan, falling back automatically elsewhere. New projects generate with AGP 9.3.1, Gradle 9.5.0, Java 17, compileSdk 36, and minSdk 24.
In the framework, SliverClipRect and SliverClipRRect add a clipOverlap option so scrolling sections can be rounded without cutting off pinned headers. ListView.separated gains itemExtentBuilder for variable-height rows with dividers, ScrollPhysics.shouldUpdate lets scroll behavior react to configuration changes, and DecorationImage.placeholder shows a fallback while background images load. MediaQueryData.reduceMotion exposes the platform accessibility setting, SemanticsRole.slider lets custom sliders announce their controls, and Escape closes tooltips without moving focus.
Under the hood, Impeller's signed distance field renderer on desktop extends its GPU path to lines, thin borders, rounded corners, and gradients, and Flutter GPU adds 2D texture arrays for batching sprite sheets and tilemaps. OpenGL shader setup moved to a background thread for a faster first frame, Mali GPU texture crashes are fixed, and the engine falls back from Vulkan to OpenGL on B-series PowerVR GPUs.
Two deprecations deserve calendar entries. package:flutter/material.dart and package:flutter/cupertino.dart are formally deprecated along with their localization delegates, and dart fix --apply --code=migrate_design_widgets handles the import rewrite. The ignoringSemantics property is gone from IgnorePointer, AbsorbPointer, and their sliver variants, and computeDryBaseline now returns null instead of asserting when a child has no baseline.