Flutter

Flutter Breaking Changes: Every Release From 3.0 On

Upgrading Flutter across one release is routine. Upgrading across five is where projects stall, because the Flutter breaking changes pile up and nobody has a single list of them. This tracker is for developers who inherited an app pinned to an old version, maintain a package that must support several, or plan a multi-release jump. It lists every migration guide Flutter has published from 3.0 to 3.47.7, grouped by the release that shipped it, with the Dart SDK that came along. In addition, it measures three things the official index does not tell you: how much of this dart fix can automate, where the index disagrees with the code, and how much deprecated API is waiting to be removed.

How These Flutter Breaking Changes Were Collected

Flutter documents its breaking changes in one index page on docs.flutter.dev, backed by a markdown file in the flutter/website repository. Each entry links to a migration guide, and most guides end with a Timeline section that names the pre-release where the change landed and the stable release that shipped it. So the data here comes from the source files rather than from memory or third-party summaries. Three short scripts read them, and their output is pasted below.

Checked 2026-10-15
Windows 11 Pro 25H2 (build 26200), Git Bash 5.2.37, curl 8.12.1, git 2.48.1
flutter/website main at d0f80ce7: breaking-changes index plus all 117 guide pages it links for 3.0 and later
flutter/flutter at 17 tags, the last patch of every stable minor from 3.0.5 to 3.47.7
dart-lang/sdk CHANGELOG.md at main, 6478280b
Release index: storage.googleapis.com/flutter_infra_release/releases/releases_windows.json
Current stable: 3.47.7, released 2026-10-08. Current beta: 3.50.0-0.1.pre, released 2026-10-14
Next scheduled re-check: 2027-01-15, or when 3.50 reaches stable

Everything below is read from files, so it does not vary between runs, and one run is reported. Two limitations matter. First, the scripts read what Flutter documents and declares. They do not upgrade a real app, so an undocumented behaviour change will not show up here. Second, the index lists only changes the Flutter team chose to write a guide for, and plenty of smaller breaks only appear in the CHANGELOG or in a package’s own release notes.

Script 1: Every Guide and Its Timeline

#!/usr/bin/env bash
# Usage: ./flutter-breaks.sh [ref]   (default: main)
# For every migration guide listed for Flutter 3.0 and later in the breaking-changes
# index, prints: release | guide | landed on master | stable release | dart fix support
ref=${1:-main}
base="https://raw.githubusercontent.com/flutter/website/$ref/sites/docs/src/content/release/breaking-changes"
index=$(curl -sSf "$base/index.md") || exit 1
echo "$index" | awk '
  /^### Released in Flutter 2\.10/ { exit }
  /^### / { rel=$0; sub(/^### (Released in Flutter )?/, "", rel) }
  /^\[[^]]+\]: \/release\/breaking-changes\// { n=split($NF, p, "/"); print rel "\t" p[n] }
' | while IFS=$'\t' read -r rel slug; do
  page=$(curl -sSf "$base/$slug.md" 2>/dev/null || curl -sSf "$base/$slug/index.md" 2>/dev/null) \
    || { echo "$rel | $slug | page missing"; continue; }
  timeline=$(echo "$page" | awk '/^## Timeline/ { on=1; next } /^## / { if (on) exit } on')
  landed=$(echo "$timeline" | grep -m1 -oE 'Landed in version:[^<]*' | sed -E 's/Landed in version: *//; s/ +$//')
  stable=$(echo "$timeline" | grep -m1 -oiE '^(in )?stable release:[^<]*' | sed -E 's/^([Ii]n )?[Ss]table release: *//; s/ +$//')
  yes=$(echo "$page" | grep -ciE 'Flutter Fix: yes|dart fix --apply|(dart|flutter) fix`?\]?(\[\])? (is|are) available')
  no=$(echo "$page" | grep -ciE "Flutter Fix: no|(isn't|is not|not) (supported|available)[^.]*(dart|flutter) fix")
  if   [ "$yes" -gt 0 ] && [ "$no" -gt 0 ]; then fix="partial"
  elif [ "$yes" -gt 0 ]; then fix="yes"
  elif [ "$no" -gt 0 ];  then fix="no"
  else fix="not mentioned"; fi
  echo "$rel | $slug | ${landed:--} | ${stable:--} | $fix"
done

The script prints one line per guide. Pass a commit SHA instead of main to reproduce this exact run. Counting the lines per release gives the number of guides each release shipped:

./flutter-breaks.sh > breaks.txt
cut -d'|' -f1 breaks.txt | sed 's/ *$//' | sort -V | uniq -c
      3 3
      4 3.3
      7 3.7
      6 3.10
      9 3.13
     13 3.16
      6 3.19
      6 3.22
      4 3.24
      6 3.27
      6 3.29
     10 3.32
     10 3.35
      6 3.38
      5 3.41
      9 3.44
      3 3.47
      4 Not yet released to stable

That is 113 guides across seventeen stable releases. The index lists one more entry under 3.10, the Dart 3 migration, but it links to dart.dev rather than to a Flutter guide, so the script skips it.

Script 2: dart fix Rules and Deprecated APIs per Release

The second script runs inside a clone of flutter/flutter. It counts the data-driven fix rules that dart fix applies, and the @Deprecated annotations still present in the framework, at each tag:

#!/usr/bin/env bash
# Usage: run inside a flutter/flutter clone; ./flutter-fix-debt.sh 3.44.9 3.47.7
# Prints, per tag: data-driven dart fix rules shipped, and @Deprecated annotations in the framework
git sparse-checkout set packages/flutter/lib >/dev/null 2>&1
for t in "$@"; do
  git -c advice.detachedHead=false checkout -q "$t" 2>/dev/null || { echo "$t | checkout failed"; continue; }
  rules=$(cat $(find packages/flutter/lib/fix_data.yaml packages/flutter/lib/fix_data -name 'fix_*.yaml' ! -name 'fix_template.yaml' -o -name fix_data.yaml 2>/dev/null) /dev/null | grep -cE '^ *- title:')
  deprecated=$(grep -rhoE '@Deprecated\(' packages/flutter/lib/src --include='*.dart' | wc -l)
  echo "$t | dart fix rules: $rules | @Deprecated in framework: $deprecated"
done

A blobless shallow fetch keeps the clone small, since only one directory gets checked out:

git init -q flutter && cd flutter
git remote add origin https://github.com/flutter/flutter.git
for t in 3.0.5 3.3.10 3.7.12 3.10.6 3.13.9 3.16.9 3.19.6 3.22.3 3.24.5 \
         3.27.4 3.29.3 3.32.8 3.35.7 3.38.10 3.41.9 3.44.9 3.47.7; do
  git fetch -q --depth=1 --filter=blob:none origin "refs/tags/$t:refs/tags/$t"
done
../flutter-fix-debt.sh $(git tag | sort -V)
3.0.5 | dart fix rules: 208 | @Deprecated in framework: 153
3.3.10 | dart fix rules: 215 | @Deprecated in framework: 143
3.7.12 | dart fix rules: 255 | @Deprecated in framework: 254
3.10.6 | dart fix rules: 262 | @Deprecated in framework: 226
3.13.9 | dart fix rules: 263 | @Deprecated in framework: 213
3.16.9 | dart fix rules: 276 | @Deprecated in framework: 237
3.19.6 | dart fix rules: 281 | @Deprecated in framework: 300
3.22.3 | dart fix rules: 314 | @Deprecated in framework: 277
3.24.5 | dart fix rules: 316 | @Deprecated in framework: 301
3.27.4 | dart fix rules: 325 | @Deprecated in framework: 315
3.29.3 | dart fix rules: 329 | @Deprecated in framework: 347
3.32.8 | dart fix rules: 344 | @Deprecated in framework: 370
3.35.7 | dart fix rules: 353 | @Deprecated in framework: 402
3.38.10 | dart fix rules: 356 | @Deprecated in framework: 415
3.41.9 | dart fix rules: 357 | @Deprecated in framework: 426
3.44.9 | dart fix rules: 381 | @Deprecated in framework: 486
3.47.7 | dart fix rules: 383 | @Deprecated in framework: 486

Up to 3.3, the fix rules lived in a single lib/fix_data.yaml. From 3.7 on, they are split into a fix_data folder by library, which is why the script looks in both places. The rule count covers the framework only, not packages like go_router that ship their own.

Script 3: Dart SDK Breaking Changes Under Each Release

Every Flutter release also pins a Dart SDK, and Dart has its own breaking changes. The third script counts the entries that the Dart CHANGELOG marks as breaking under each x.y.0 heading:

#!/usr/bin/env bash
# Usage: ./dart-breaks.sh CHANGELOG.md 2.17 3.13
# Prints each entry that opens with "**Breaking change" under the x.y.0 heading
# of every Dart release in the range, one line per entry
awk -v lo="$2" -v hi="$3" '
  function key(v,  a) { split(v, a, "."); return a[1]*1000 + a[2] }
  /^## [0-9]+\.[0-9]+\.[0-9]+/ { split($2, a, "."); ver=a[1]"."a[2]; on=(a[3]=="0" && key(ver)>=key(lo) && key(ver)<=key(hi)); next }
  on && /^ *[-*] +\*\*[Bb]reaking [Cc]hange/ { line=$0; sub(/^[ *-]+/, "", line); print ver " | " substr(line, 1, 110) }
' "$1"
curl -sSfO https://raw.githubusercontent.com/dart-lang/sdk/main/CHANGELOG.md
./dart-breaks.sh CHANGELOG.md 2.17 3.13 | cut -d'|' -f1 | sort -V | uniq -c
      7 2.17 
      7 2.18 
     13 2.19 
     12 3.0 
      5 3.1 
     14 3.2 
      6 3.3 
      4 3.4 
      8 3.5 
      4 3.6 
      2 3.7 
      1 3.8 
      2 3.10 
      1 3.12 
      4 3.13

Dart 3.9 and 3.11 print nothing, which means their CHANGELOG sections have no entry opening with “Breaking change”. Many of these entries only touch dart:js_interop, dart:io or the VM embedding API, so a typical mobile app hits far fewer of them than the count suggests.

Flutter Breaking Changes by Release: Summary Table

This is the one-screen view. The Dart column is the SDK bundled with the first stable patch of each minor, from the release index. The last column names the changes most likely to stop an app from compiling or to change what users see.

FlutterFirst stableDartFlutter guidesDart breaking entriesThe changes that bite
3.02022-05-112.1737Expired deprecations removed, Chip tooltip rename
3.32022-08-302.1847Desktop scroll controller default
3.72023-01-242.19713Context menu builder, maybeOf split
3.102023-05-103.0612Dart 3: sound null safety required, window deprecated
3.132023-08-163.195AppLifecycleState.hidden, RouteInformation.uri
3.162023-11-153.21314Material 3 on by default, TextScaler
3.192024-02-153.366KeyEvent migration, declarative Gradle plugins
3.222024-05-133.464MaterialState to WidgetState, KitKat dropped
3.242024-08-063.548Generic PopScope, onDidRemovePage
3.272024-12-113.664Wide gamut Color, edge-to-edge on Android 15
3.292025-02-123.762v1 Android embedding removed, tall-style formatter
3.322025-05-203.8101flutter_gen synthetic package removed
3.352025-08-143.9100RadioGroup, default abiFilters
3.382025-11-123.1062UIScene lifecycle, predictive back transition
3.412026-02-113.1150Material 3 token update, Linux merged threads
3.442026-05-183.1291Built-in Kotlin for AGP 9, IconData final
3.472026-08-123.1334headingLevel semantics, no final parameters at language 3.13

The two busiest releases, 3.16 and 3.32, are not the most disruptive ones. In practice, a single guide like Dart 3 in 3.10 or Material 3 in 3.16 can cost more than ten small deprecations.

Every Flutter Breaking Change, Release by Release

The tables below list all 113 guides from the index, linked to the official migration page. Titles are the index’s own. The prose under each group points out the ones that tend to cause real work.

Flutter 3.0 to 3.7: Removals and Desktop Defaults

ReleaseMigration guide
3.0Deprecated API removed after v2.10
3.0Page transitions replaced by ZoomPageTransitionsBuilder
3.0Migrate useDeleteButtonTooltip to deleteButtonTooltipMessage of Chips
3.3Adding ImageProvider.loadBuffer
3.3Default PrimaryScrollController on Desktop
3.3Trackpad gestures can trigger GestureRecognizer
3.3Migrate a Windows project to set version information
3.7Replaced parameters for customizing context menus with a generic widget builder
3.7Deprecated API removed after v3.3
3.7iOS FlutterViewController splashScreenView made nullable
3.7Migrate of to non-nullable return values, and add maybeOf
3.7Removed RouteSettings.copyWith
3.7ThemeData’s toggleableActiveColor property has been deprecated
3.7Migrate a Windows project to support dark title bars

Most of this era is cleanup. The deprecation-removal guides at 3.0 and 3.7 only bite code that ignored deprecation warnings for several releases. The maybeOf split in 3.7 is the one that touches app code broadly, because of methods that used to return nullable values now throw when nothing is found.

Flutter 3.10 to 3.16: Dart 3 and Material 3

ReleaseMigration guide
3.10Deprecated API removed after v3.7
3.10Insert Content Text Input Client
3.10Deprecated the window singleton
3.10Resolve the Android Java Gradle error
3.10Require one data variant for ClipboardData constructor
3.10“Zone mismatch” message
3.13Added missing dispose() for some disposable objects in Flutter
3.13Deprecated API removed after v3.10
3.13Added AppLifecycleState.hidden
3.13Moved ReorderableListView’s localized strings
3.13Removed ignoringSemantics
3.13Deprecated RouteInformation.location
3.13Updated EditableText scroll into view behavior
3.13Migrate a Windows project to ensure the window is shown
3.13Updated Checkbox.fillColor behavior
3.16Migrating to Material 3
3.16Migrate ShortcutActivator and ShortcutManager to KeyEvent system
3.16The ThemeData.useMaterial3 property is now set to true by default
3.16Deprecated API removed after v3.13
3.16Customize tabs alignment using the new TabBar.tabAlignment property
3.16Deprecate textScaleFactor in favor of TextScaler
3.16Android 14 nonlinear font scaling enabled
3.16Deprecate describeEnum and update EnumProperty to be type strict
3.16Deprecated just-in-time navigation pop APIs for Android Predictive Back
3.16Deprecated Paint.enableDithering
3.16Updated default text styles for menus
3.16Windows: External windows should notify Flutter engine of lifecycle changes
3.16Windows build path changed to add the target architecture

Flutter 3.10 is the hard wall for old code, because it shipped Dart 3. Dart 3 runs only sound null safe code, so any package without null safety stops resolving, and the Dart 3 migration guide covers the language side. By contrast, 3.16 compiles fine and then looks different. Turning useMaterial3 on by default changes colors, shapes and spacing across every screen, which is why golden tests fail in bulk on that upgrade. Teams that are not ready can set useMaterial3: false while they migrate screen by screen, and the flag still exists in 3.47.7. The AppLifecycleState.hidden value in 3.13 also breaks exhaustive switch statements on the enum, because Dart 3 requires every value to be handled.

Flutter 3.19 to 3.27: Keyboard, Navigation and Color

ReleaseMigration guide
3.19Deprecated API removed after v3.16
3.19Migrate RawKeyEvent/RawKeyboard system to KeyEvent/HardwareKeyboard system
3.19Deprecate imperative apply of Flutter’s Gradle plugins
3.19Default multitouch scrolling
3.19Accessibility traversal order of tooltip changed
3.19Stop generating AssetManifest.json
3.22Deprecated API removed after v3.19
3.22Rename MaterialState to WidgetState
3.22Introduce new ColorScheme roles
3.22Dropping support for Android KitKat
3.22Nullable PageView.controller
3.22Rename MemoryAllocations to FlutterMemoryAllocations
3.24Navigator’s page APIs breaking change
3.24Generic types in PopScope
3.24Deprecate ButtonBar in favor of OverflowBar
3.24New APIs for Android plugins that render to a Surface
3.27Color wide gamut support
3.27Component theme normalization
3.27Deep links flag change
3.27Material 3 Tokens Update in Flutter
3.27Remove invalid parameters for InputDecoration.collapsed
3.27Set default for SystemUiMode to Edge-to-Edge

The declarative Gradle plugin guide in 3.19 is the one that still breaks old Android folders today. Projects created before 3.16 apply the Flutter Gradle plugin imperatively from app/build.gradle, a setup deprecated since this release, so plan the move to the plugins {} block before a later release drops it. Meanwhile, the 3.24 navigation changes hit anyone using Navigator.pages or a custom router, since onPopPage gave way to onDidRemovePage.

Flutter 3.27 is the quiet one that touches the most files. The wide gamut Color work moved color channels to floating point and added withValues. At 3.47.7, dart:ui marks both withOpacity and the integer getters such as red as @Deprecated:

import 'package:flutter/material.dart';

// Before 3.27: alpha as a double opacity, channels as 0-255 ints
final overlay = Colors.black.withOpacity(0.4);
final redChannel = Colors.teal.red;

// 3.27 and later: withValues keeps the color space, channels are 0.0-1.0 doubles
final overlayNew = Colors.black.withValues(alpha: 0.4);
final redChannelNew = Colors.teal.r;

The old calls still compile with a deprecation warning, which is exactly why they survive in large codebases. Any code that does math on the integer channels, such as a color-contrast helper, needs real review rather than a search and replace. The deep links flag change in the same release also matters if a third-party package such as a marketing SDK handles incoming links, because Flutter now claims them by default.

Flutter 3.29 to 3.35: Android Embedding, l10n and Theming

ReleaseMigration guide
3.29Removal of v1 Android embedding Java APIs
3.29Deprecate WebGoldenComparator
3.29Deprecate ThemeData.dialogBackgroundColor in favor of DialogThemeData.backgroundColor
3.29ImageFilter.blur default tile mode automatic selection
3.29Updated Material 3 progress indicators
3.29Updated Material 3 Slider
3.32Deprecate SystemContextMenuController.show
3.32Deprecate ExpansionTileController in favor of ExpansibleController
3.32Deprecate RouteTransitionRecord.markForRemove in favor of markForComplete
3.32Deprecate ThemeData.indicatorColor in favor of TabBarThemeData.indicatorColor
3.32Localized messages are generated into source, not a synthetic package
3.32Material Theme System Updates
3.32.flutter-plugins-dependencies replaces .flutter-plugins
3.32Changing the default goldenFileComparator for integration_tests
3.32Deprecate InputDecoration.maintainHintHeight in favor of InputDecoration.maintainHintSize
3.32Underdamped spring formula changed
3.35Component theme normalization updates
3.35Deprecate DropdownButtonFormField value parameter in favor of initialValue
3.35Deprecate app bar color
3.35Redesigned the Radio Widget
3.35Removed semantics elevation and thickness
3.35The Form widget no longer supports being a sliver
3.35Flutter now sets default abiFilters in Android builds
3.35Merged threads on macOS and Windows
3.35The Visibility widget is no longer focusable by default when maintainState is enabled
3.35$FLUTTER_ROOT/version replaced by $FLUTTER_ROOT/bin/cache/flutter.version.json

Three of these break builds rather than widgets. First, 3.29 removed the v1 Android embedding, so any plugin still using removed v1 classes such as io.flutter.app.FlutterPluginRegistry or ShimPluginRegistry fails to compile and has to be replaced or forked. Second, 3.32 stopped generating the synthetic package:flutter_gen. Every import 'package:flutter_gen/gen_l10n/app_localizations.dart' must point at the generated source file instead, and generate: true is now mandatory. The Flutter localization guide shows the l10n.yaml setup this relies on. Third, 3.35 started setting abiFilters automatically, which collides with custom filters in build.gradle.

The RadioGroup redesign in 3.35 is the widget change most apps will notice. Individual Radio.groupValue and Radio.onChanged are deprecated, and the group now owns the selection:

import 'package:flutter/material.dart';

enum ShippingSpeed { standard, express }

class ShippingPicker extends StatelessWidget {
  const ShippingPicker({super.key, required this.value, required this.onChanged});

  final ShippingSpeed value;
  final ValueChanged<ShippingSpeed?> onChanged;

  @override
  Widget build(BuildContext context) {
    // 3.35+: RadioGroup holds groupValue and onChanged for every Radio below it
    return RadioGroup<ShippingSpeed>(
      groupValue: value,
      onChanged: onChanged,
      child: const Column(
        children: [
          RadioListTile(value: ShippingSpeed.standard, title: Text('Standard')),
          RadioListTile(value: ShippingSpeed.express, title: Text('Express')),
        ],
      ),
    );
  }
}

The payoff is that the group handles keyboard navigation between options, which the old per-radio wiring could not do.

Flutter 3.38 to 3.47: Platform Lifecycles and Built-in Kotlin

ReleaseMigration guide
3.38CupertinoDynamicColor wide gamut support
3.38Deprecate OverlayPortal.targetsRootOverlay
3.38Deprecate SemanticsProperties.focusable and SemanticsConfiguration.isFocusable
3.38SnackBar with action no longer auto-dismisses
3.38The default page transition on Android is now PredictiveBackPageTransitionBuilder
3.38UISceneDelegate adoption
3.41Merged threads on Linux
3.41FontWeight also controls the weight attribute of variable fonts
3.41Deprecate containsSemantics in favor of isSemantics
3.41Deprecate findChildIndexCallback in favor of findItemIndexCallback in separated constructors
3.41Material 3 tokens update
3.44Changing RawMenuAnchor close order
3.44Deprecate onReorder callback
3.44Deprecated cacheExtent and cacheExtentStyle
3.44Deprecate TextInputConnection.setStyle
3.44IconData class marked as final
3.44Large screen orientation and resizability restrictions ignored on Android 17
3.44ListTile reports an error in debug when wrapped in a colored widget
3.44Migrating Flutter Android projects to built-in Kotlin
3.44Page transition builders reorganization
3.47OpenGL ES render-to-texture content is stored top-down
3.47Update semantics header and headingLevel behavior on iOS and Android
3.47Removal of describeEnum

The recent releases break platform integration more than widgets. The UIScene adoption in 3.38 follows Apple’s own requirement, and the Flutter version matrix covers how Xcode 27 makes it mandatory at launch. On Android, 3.44 is the AGP 9 release: AGP 9 builds Kotlin itself, so a project that still applies the separate Kotlin Gradle plugin fails to build unless it sets android.builtInKotlin=false as a stopgap. Flutter 3.47 then added support for turning built-in Kotlin on once every plugin has migrated.

Two smaller changes deserve a test run. The 3.44 ListTile check now reports an error in debug when a Container or ColoredBox with a color sits between the tile and its Material, a layout that many custom list designs use. Likewise, in 3.47 the header semantics flag became a no-op on iOS and Android, so screen readers stop announcing headings until the code moves to headingLevel. The Flutter accessibility guide covers how to verify that with TalkBack and VoiceOver.

What Is the Biggest Flutter Breaking Change Since 3.0?

The biggest Flutter breaking change since 3.0 is the move to Dart 3 in Flutter 3.10, because it stopped every package without sound null safety from resolving at all. Material 3 becoming the default in 3.16 changed more pixels, but it never blocked a build. For apps already past 3.10, the Android toolchain changes in 3.19 and 3.44 cost the most time.

Does dart fix Handle Flutter Breaking Changes?

Partly, and far more than the guides admit. Of the 113 guides, only 11 say that dart fix handles some or all of the change, according to the script’s last column:

cut -d'|' -f5 breaks.txt | sort | uniq -c
      3  no
    103  not mentioned
      5  partial
      6  yes

That total includes the four unreleased guides, two of which mention dart fix. Yet Flutter 3.47.7 ships 383 data-driven fix rules, up from 208 in 3.0.5. Many guides never mention the fix that exists for them. So the guide is the wrong place to decide whether automation helps. The analyzer is the right place:

# See every change dart fix would make, grouped by file, without touching anything
dart fix --dry-run

# Apply them, then let the analyzer list what is left for a human
dart fix --apply
flutter analyze

Run this after each minor upgrade, not once at the end of a multi-release jump. Each step then produces a diff small enough to review, and any failure traces back to one release. In practice, a single dart fix pass across eight releases rewrites hundreds of files at once, and nobody reviews that diff properly.

Where the Breaking-Change Index Disagrees With the Code

The index is maintained by hand, and the script surfaced places where it disagrees with the guides it links to, or with the framework itself. These are the guides whose own Timeline names a different stable release than the index heading:

Index headingGuideGuide’s own Timeline
3.47Removal of describeEnumTBD
3.35Deprecate app bar color3.35.4
3.35$FLUTTER_ROOT/version replacedNot published yet
3.16Migrate ShortcutActivator and ShortcutManager to KeyEvent3.19.0

The describeEnum row is the important one, because the index files the removal under 3.47. The framework source says otherwise:

curl -sSf https://raw.githubusercontent.com/flutter/flutter/3.47.7/packages/flutter/lib/src/foundation/diagnostics.dart \
  | grep -n -A4 '^@Deprecated' | grep -B4 'String describeEnum'
3016:@Deprecated(
3017-  'Use the `name` getter on enums instead. '
3018-  'This feature was deprecated after v3.14.0-2.0.pre.',
3019-)
3020-String describeEnum(Object enumEntry) {

The function is still present in 3.47.7, deprecated but working, and the same check against the master branch on 2026-10-15 still found it. Code that calls describeEnum builds today. However, it will break whenever the removal does land, so replacing it with the enum’s name getter now costs one line per call. In addition, nine guides from 3.3 to 3.44 have no stable-release line in the standard form, mostly Windows and Android project migrations. For those, the index heading is the only date available.

Deprecated APIs Are Piling Up: No Bulk Removal Since 3.22

Flutter used to clear out old deprecations in batches. The index has a “Deprecated API removed after” guide for 3.0, 3.7, 3.10, 3.13, 3.16, 3.19 and 3.22, and the @Deprecated count from Script 2 drops at 3.10, 3.13 and 3.22. After 3.22, there are no more batch-removal guides, and the count climbs every release, from 277 to 486. Here is where those 486 annotations sit at 3.47.7:

for d in material widgets services cupertino rendering painting semantics gestures foundation; do
  printf "%s " $d; grep -rhoE '@Deprecated\(' packages/flutter/lib/src/$d --include='*.dart' | wc -l
done
material 165
widgets 161
services 66
cupertino 39
rendering 27
painting 12
semantics 8
gestures 4
foundation 4

The count did not move between 3.44.9 and 3.47.7, which fits the code freeze on Material and Cupertino inside the framework that began with 3.44. That freeze is part of the move to standalone packages covered in the material_ui migration guide. The practical takeaway: whenever the next bulk removal arrives, it will be the largest in Flutter’s history. Code that leans on deprecated members across several releases will break all at once, so clearing deprecation warnings each release is the cheapest insurance available.

Flutter Breaking Changes Coming in 3.50

Four guides sit under “Not yet released to stable” in the index. Two of them already shipped in the first 3.50 beta, 3.50.0-0.1.pre, released 2026-10-14:

ChangeLanded inWho it affects
Stricter Android Intent verification for app entrypoints3.50.0-0.1.preApps that set an initial route or Dart entrypoint from another app’s Intent
Restrict Android engine flags in release mode3.50.0-0.1.preRelease builds that read engine flags from Intent extras
DropdownButton enabled property, optional onChangedNot yetCode that disables a dropdown by passing a null onChanged
Standalone material_ui and cupertino_ui packagesNo timeline yetEvery app and package importing package:flutter/material.dart

The two Android Intent changes are security hardening. In release mode, external Intent objects can no longer set the initial route, Dart entrypoint or cached engine ID unless the embedder verifies they came from the app itself. As a result, a deep-link flow that relied on EXTRA_INITIAL_ROUTE from another app quietly falls back to the manifest default. The material_ui deprecation is the large one, because it puts a deprecation diagnostic on every file that imports Material.

How to Upgrade Across Several Flutter Releases

A jump across several minors goes faster one minor at a time, because each step has a short list of guides and its own fix rules:

  1. Commit a clean flutter analyze and a green test run on the current version, so every new warning is attributable
  2. Read this page’s rows for the next minor only, and check which guides touch code you own
  3. Switch the SDK to the next minor, with FVM or by checking out the release tag inside the Flutter SDK folder
  4. Run flutter pub upgrade --major-versions and fix dependency conflicts before touching app code
  5. Run dart fix --apply, then flutter analyze, and fix what remains by hand
  6. Run unit, widget and golden tests, and review golden diffs instead of regenerating them blindly
  7. Build Android and iOS release artifacts, then commit and repeat for the next minor

Step 6 is where behaviour changes surface, since only a test sees a SnackBar that stopped auto-dismissing. A reasonable Flutter testing strategy catches most of them, and golden tests catch the visual ones. Toolchain floors for each step live in the version matrix linked above.

Real-World Scenario: An App Stuck on 3.19

Consider a mid-sized app with 30 to 40 screens, maintained part-time by a small team, still on Flutter 3.19 in late 2026. App Store uploads have already started failing because Apple requires Xcode 26, and Flutter 3.19 predates every Xcode 26 fix. So the upgrade is no longer optional, and the target is 3.47.

Jumping straight to 3.47 produces a wall of errors that mixes eight releases of changes, and it is hard to tell which fix belongs to which guide. Stepping through 3.22, 3.24, 3.27 and onward splits that wall into chunks a single developer can clear in a day or two each. The heavy steps are predictable from the tables. The 3.22 step brings WidgetState renames, which dart fix handles. At 3.24, the PopScope generics land, while 3.27 adds hundreds of withOpacity warnings. Next, 3.29 breaks any plugin still on the v1 embedding, and 3.32 breaks every flutter_gen import. Finally, 3.44 brings the AGP 9 Kotlin migration.

The trade-off is time against risk. Stepping minor by minor takes several weeks of part-time work rather than one long weekend. However, each step ends in a green build that can ship, so a release that cannot wait does not have to.

When to Use This Flutter Breaking Changes Tracker

  • Planning an upgrade that spans more than one Flutter minor
  • Supporting a package across a range of Flutter versions, where each guide marks an API boundary
  • Estimating upgrade effort before committing to a deadline
  • Triaging a build that broke after someone ran flutter upgrade on a shared CI image
  • Auditing an inherited codebase to see which migrations it skipped

When NOT to Rely on This Tracker Alone

  • Your app depends heavily on third-party packages, which break on their own schedule and changelogs
  • You need the full list of behaviour changes, since many never get a migration guide
  • The upgrade is a single patch release, where the CHANGELOG hotfix list is the better source
  • You are debugging a native build error, which usually traces to Xcode, Gradle or AGP floors rather than to a framework guide
  • A guide’s timeline reads TBD or is missing, so confirm against the source tag first

Common Mistakes with Flutter Breaking Changes

  • Jumping several minors at once and running dart fix only at the end
  • Treating a deprecation warning as cosmetic, then losing the API in a bulk removal
  • Regenerating golden files after a Material 3 or token update without reviewing the diffs
  • Assuming a guide that never mentions dart fix has no automated fix
  • Trusting the index heading over the guide’s own timeline, as with describeEnum
  • Forgetting that the Dart SDK upgrades too, along with its own breaking changes
  • Pinning a plugin fork for the v1 embedding or flutter_gen and never revisiting it

Keeping This Flutter Breaking Changes Tracker Current

This page is a tracker, reviewed every three months and on each stable minor release, with the next re-check on 2027-01-15. To check a newer release, run Script 1 with no arguments and compare its output with the tables. New guides appear at the top of the output, and a changed Timeline shows up as a different value in the stable column. If a line prints “page missing”, the website repository moved a file, and the base path in the script needs updating.

Next Steps

Flutter breaking changes from 3.0 to 3.47 come down to 113 migration guides, and only a handful of them cause real work for a typical app. Those are Dart 3, Material 3, the KeyEvent and Gradle plugin migrations, the v1 embedding removal, flutter_gen, and built-in Kotlin. Before your next upgrade, run dart fix --dry-run on the current version and clear every deprecation warning while it is still a warning. Then step through the tables one minor at a time. If the upgrade also involves Android rendering, the Impeller migration guide covers the renderer switch that ships alongside these changes.