Skip to content
ExponaBLOG
FLUTTER · DAY 096 / 100

Upgrading Flutter versions: patterns, pitfalls and fixes

Stay current without breaking builds.

Expona Admin6 min read

I've watched teams delay Flutter version upgrades for months, only to find themselves scrambling when support ends or critical security patches land. Upgrading Flutter versions doesn't have to feel like defusing a bomb—with the right approach, you can stay current, avoid broken builds, and keep your app moving forward. Over nearly six years shipping production platforms, I've learned that a methodical upgrade process saves far more time than the caution that causes delay.

Why upgrading matters

Flutter moves fast. New stable releases arrive roughly every three months, bringing performance improvements, new widgets, platform updates (iOS, Android), and most importantly, security fixes. Staying on an old version isn't really "safe"—it's risky. You miss bug fixes that others depend on, you fall behind when your dependencies update, and you make hiring harder when candidates see your codebase is two years behind.

That said, upgrades do carry real friction. Breaking changes happen. Deprecations accumulate. Platform behavior shifts. The goal isn't to upgrade for upgrade's sake; it's to upgrade deliberately, with enough process that you catch issues before they reach production.

Read release notes — Breaking changes and deprecations

This step sounds obvious and gets skipped constantly. Before you touch a single line of code, spend twenty minutes reading the release notes for the version you're moving to. I'm serious about this. Every stable release comes with a changelog that documents exactly what changed, what's deprecated, and what breaks.

Flutter publishes these on their GitHub releases page and in the What's new section of the documentation. Look specifically for:

  • Breaking changes — APIs removed, behavior changes that require code updates
  • Deprecations — APIs marked for future removal, often with migration paths
  • Platform-specific notes — iOS and Android version requirements, Xcode/Android SDK updates needed
  • Performance or rendering changes — Things that might affect your app visually or behaviorally

For example, moving from Flutter 3.10 to 3.13, there were changes to how Image.network handled certain caching scenarios, and deprecated APIs in the Material library around text input handling. Knowing this upfront meant I could scan my codebase and mark the affected areas before running tests.

Create a simple checklist from the release notes. Write it down, or paste it into your ticket. This becomes your roadmap and proof that you did your homework when something unexpected surfaces.

Use dart fix — Automated migrations for many changes

Once you've upgraded Flutter (via flutter upgrade or specifying a version in your pubspec.yaml), the Dart tooling can handle a surprising number of migrations automatically. The dart fix command is your friend here.

Run this after upgrading:

dart
dart fix --dry-run

The --dry-run flag shows you what changes would be made without applying them. Review the output carefully. You'll often see things like:

  • Null-safety migrations (if you're still on older code)
  • Imports rewritten for moved APIs
  • Deprecated parameter removals
  • Type annotations added or clarified

Once you're confident, run it for real:

dart
dart fix --apply

In a recent project upgrading from 3.7 to 3.16, dart fix handled about 80% of the tedious work—rewriting deprecated showDialog calls, updating navigation APIs, fixing a dozen small type mismatches. Yes, there were still manual fixes needed, but the tooling saved hours.

After running dart fix, always run your static analyzer to catch anything it missed:

dart
dart analyze

Watch for warnings and errors. Some are noise, but deprecation warnings are gold—they point to APIs you should update proactively before they're removed entirely.

Upgrade dependencies — Packages often need updates too

Here's the hidden work that trips up most people: your Flutter upgrade often requires updates to your dependencies. If you're on a three-month-old version of http or provider or firebase_core, those packages might not declare compatibility with your new Flutter version, even if they'd work fine.

Start with a clean check:

dart
flutter pub outdated

This shows you every package and where it stands relative to available versions. You'll see:

  • Current — What you have installed
  • Upgradable — Latest version within your version constraints
  • Resolvable — Latest version that satisfies all constraints (might require dependency updates elsewhere)
  • Latest — Newest version available

Don't blindly jump everything to the latest version. Instead, be strategic. Upgrade packages in this order:

  1. Core Flutter dependencies first (if you have any custom Flutter engine integrations)
  2. Platform-level packages (Firebase, native bridges, etc.)
  3. State management and UI packages (provider, riverpod, get_it)
  4. Utility packages last (http, dio, serialization libraries)

After each logical group, run your tests. If something breaks, you know which packages caused it.

A concrete example: I was upgrading an e-commerce app and found that firebase_core needed to jump from 2.1 to 2.14, and that change meant firebase_auth, firebase_database, and cloud_firestore all had to move together. By checking compatibility ahead of time, I updated all four in a single PR rather than discovering the issue during testing.

Use flutter pub upgrade to get the latest compatible versions across the board, or upgrade specific packages:

dart
flutter pub upgrade firebase_core cloud_firestore

After upgrading, run flutter pub get and let the analyzer scan again:

dart
flutter pub get
dart analyze

Watch especially for conflicting constraints errors—these mean your dependency versions can't all coexist and need refinement.

Test on devices — Rendering and platform changes need real testing

This is where most upgrade work actually lives, and it cannot be skipped or automated away. A new Flutter version can change:

  • How widgets render (shadow, padding, spacing on Material components)
  • Gesture handling and touch responsiveness
  • Text rendering and font metrics
  • WebSocket and platform channel behavior
  • Asset loading and image rendering
  • Navigation stack management

None of these show up in static analysis. They only show up when you interact with the app on real devices.

I recommend this testing sequence:

  1. Unit and widget tests first — Run your test suite. You'll catch obvious logic errors and API mismatches immediately.
  2. Emulator smoke test — Launch on Android and iOS emulators. Navigate the major flows, check layout.
  3. Physical device testing — This is non-negotiable. Rendering, performance, and gesture behavior can differ significantly on real hardware. Test on at least one modern Android device and one iOS device.
  4. Regression testing of critical paths — Authentication, payments, core user flows. If these work, everything usually does.

Set aside time for this. In my experience, a full upgrade on a production app takes a full day of testing per developer just to feel confident. That's not excessive—it's realistic.

Common mistakes

Upgrading everything at once. It's tempting to run flutter upgrade, then flutter pub upgrade, then deploy. Don't. Separate the Flutter version change from the package changes. When something breaks, you'll know which step caused it.

Skipping release notes. "It's just a patch version, how different can it be?" Plenty. I once spent hours debugging a rendering issue that turned out to be a documented layout change in a minor version. The release notes mentioned it in one line.

Testing only on emulators. Emulators lie. They hide performance issues, gesture lag, and platform-specific bugs. Real devices are where the truth lives.

Not backing up your working branch. Before you start an upgrade, create a backup branch. If the upgrade goes sideways and you need to bail out, you have an escape route. git checkout -b backup/before-flutter-upgrade takes ten seconds and saves your sanity.

Upgrading in the middle of feature work. Upgrades should be their own, isolated PR. Don't mix them with feature development. Keep concerns separate so you can review and test each cleanly.

Key takeaways

  • Always read release notes before upgrading. Breaking changes and deprecations are documented for a reason.
  • Use dart fix --dry-run to preview automated migrations, then apply them. It handles a lot of busywork.
  • Upgrade dependencies deliberately and in groups. Check compatibility early, and test after each logical batch.
  • Test on real devices. Emulators catch logic errors; physical hardware catches rendering, performance, and gesture issues.
  • Isolate upgrade work in its own PR with its own test cycle. Don't mix version upgrades with feature development.
  • Keep a backup branch before you start. Upgrades usually go smoothly, but you want an escape route if they don't.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles