Note: this article was originally published on my site: ishaqhassan.dev/blog/flutter-3-47-my-commits.html
Flutter 3.47.0 went out on the stable channel on August 12, 2026, carrying Dart 3.13.0. If you ran flutter upgrade this week, you are now running some of my code.
That is not a figure of speech. Nine commits with my name on them are inside the 3.47.0 tag: seven in the framework and tooling, two more in the DevTools bundle that ships with it. Four of them are listed by number in the official release notes.
This article is the full accounting: what each commit actually does, which of them reached stable for the first time in this release, and, more usefully for you, exactly how to check whose code your own SDK is running instead of taking anybody's word for it.
Flutter 3.47 at a glance
- Version: Flutter 3.47.0, stable channel. As of today it is the latest stable Flutter release.
- Release date: August 12, 2026.
- Dart SDK: 3.13.0. DevTools: 2.60.0.
- Previous stable: the 3.44 line, which ended at the 3.44.9 hotfix on August 6, 2026. Flutter skipped 3.45 and 3.46 on stable entirely.
- Size of the jump: 1,358 commits ahead of 3.44.0.
-
Headline changes: standalone
material_uiandcupertino_uipackages, Impeller as the default renderer on desktop, Widget Previews stable, iOS 15 and macOS 12 minimums.
What actually shipped on August 12
The headline features of 3.47 are the ones everyone wrote about: the Material and Cupertino libraries becoming standalone material_ui and cupertino_ui packages on pub.dev, Impeller becoming the default renderer on macOS, Windows and Linux, Widget Previews graduating to stable, and the Apple platform bump that raises the minimum to iOS 15 and macOS 12 ahead of Xcode 27.
Underneath those headlines, 3.47.0 is 1,358 commits ahead of 3.44.0, the previous stable. Flutter skipped 3.45 and 3.46 on the stable channel entirely, so this release swallowed nearly three months of merged work. Most of that work is invisible: a parameter here, a corrected doc comment there, a template fix that only matters when you create a new project.
A release is a headline plus a thousand small corrections. The headline gets the blog post. The corrections are what you actually hit on a Tuesday afternoon.
The nine commits, one by one
Two API additions you can use today
Add clipBehavior to AnimatedCrossFade (66 lines, 2 files). AnimatedCrossFade wrapped its children in a hardcoded ClipRect that you could not reach. Anything that painted outside the child bounds got sliced: box shadows, glows, and the stroke of a CircularProgressIndicator mid-crossfade. An earlier attempt to fix this by deleting the ClipRect outright was closed, because removing clipping silently changes existing layouts. So this one takes the boring path and exposes the knob instead, defaulting to Clip.hardEdge:
AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
crossFadeState: state,
clipBehavior: Clip.none, // shadows and indicator strokes survive now
firstChild: const CardWithShadow(),
secondChild: const CircularProgressIndicator(),
)
Add scrollPadding to DropdownMenu (37 lines, 2 files). DropdownMenu wraps a TextField internally, and TextField has always accepted scrollPadding, the padding kept around the field when the keyboard pushes it into view. DropdownMenu simply never forwarded it, so a dropdown at the bottom of a form would scroll to sit flush against the keyboard. SearchBar already exposed the property, which made the argument easy: this is a consistency gap, not a new idea.
DropdownMenu<String>(
scrollPadding: const EdgeInsets.only(bottom: 120),
dropdownMenuEntries: entries,
)
One template fix every new Android build inherits
Use double quotes in the settings.gradle.kts template (one line changed). When the plugin template was renamed from settings.gradle to settings.gradle.kts, the string stayed in Groovy style with single quotes. Kotlin only accepts single quotes for character literals, so the generated file was invalid Kotlin:
// before, invalid Kotlin
rootProject.name = '{{projectName}}'
// after
rootProject.name = "{{projectName}}"
One character on each side of a string, in a file nobody reads, that every generated Android plugin project starts from. That is the shape of the highest-leverage fixes: tiny diff, enormous blast radius.
Four documentation fixes that save real debugging hours
Docs commits are the ones people dismiss. They are also the ones that quietly delete entire afternoons from other engineers' lives.
-
Disposal guidance on CurvedAnimation and CurveTween.
CurvedAnimationregisters a status listener on its parent controller in its constructor, and only removes it indispose(). Create one insidebuild()and you leak a listener on every single rebuild. The class docs now say so out loud, along with the rule not to build one insidebuild(). - RouteAware.didPushNext timing. The docs claimed the callback fires when the current route is no longer visible. Tracing the source shows it fires synchronously during the push, before the transition animation finishes, while your route is still partly on screen. If you were pausing a video or stopping a camera in that callback, the old wording was actively misleading.
-
LicenseRegistry points at NOTICES, not LICENSE. The docs told you the bundled license asset was called
LICENSE. The tool actually producesNOTICES(orNOTICES.Zcompressed), and the binding has been loading that all along. Anyone writing a custom license screen was hunting for a file that does not exist. -
moveStep direction on dragUntilVisible. In widget tests,
dragUntilVisibletakes amoveStepoffset whose sign convention is the opposite ofscrollUntilVisible: to reveal content below the fold you drag with a negative dy, because the finger swipes up. People kept getting it backwards and filing issues about it. The dartdoc now spells out all four directions.
Two DevTools fixes in the 2.60.0 bundle
Flutter 3.47 pins DevTools 2.60.0, up from 2.57.0 in the 3.44 line. Two fixes of mine ride in that jump:
-
SplitPane range error when the child count changes.
SplitPanecached its fraction list once ininitState. Toggle a panel in or out with a collection-ifand the cached list kept the old length while the children shrank, so the next layout threwRangeError (index): Index out of range. The fix addsdidUpdateWidgetto rebuild the fractions when the child count changes. - Network search field stays usable after clearing calls. The Network tab tied the search field's enabled state to whether any requests were currently recorded. Hit clear, and the field went dead, so you could not prepare a filter before the next request arrived. Now it stays enabled, matching every other search field in DevTools.
Six of the nine reached stable for the first time
Here is the nuance most "my code shipped" posts skip, including my own first draft of this one.
All nine commits are inside the 3.47.0 tag. But three of them, the Gradle template fix, the RouteAware doc correction and scrollPadding on DropdownMenu, all merged in March and already went out with 3.44.0 on May 18. They have been in production Flutter apps for three months. They are in 3.47 the same way every other commit in Flutter's history is: they were never removed.
The six that reached the stable channel for the first time in 3.47 are the four framework and test-API items from April, May and June, plus both DevTools fixes.
That gap is worth internalising if you contribute to Flutter. A merged pull request is not a shipped pull request. It lands on main, waits for a beta cut, then waits for a stable cut. My clipBehavior work merged on April 23 and reached stable users on August 12: 111 days between the purple badge and the first developer who could actually type clipBehavior: and have it compile. Flutter skipping 3.45 and 3.46 on stable stretched that queue further.
How to check whose code you are running
You should not believe any of the above because I wrote it. Every claim here is checkable in about a minute, and the same technique works for any contributor and any release.
1. Know your version.
flutter --version
# Flutter 3.47.0 · channel stable · Dart 3.13.0
2. Filter the release tag by author on GitHub. Commit listings accept an author filter as a query parameter, so this URL is the whole trick:
https://github.com/flutter/flutter/commits/3.47.0?author=ishaquehassan
Swap the tag for the version you are on and the username for whoever you are curious about. Your own name works too, and it is a good day when it does.
3. Ask your local SDK directly. Your Flutter install is a git clone, so it can answer this offline:
cd "$(dirname "$(readlink -f "$(which flutter)")")/.."
git fetch --tags
git log --oneline --author=ishaq 3.47.0 | cat
4. Settle a single pull request with a yes or no. Take the merge commit SHA from the pull request page and ask git whether it is an ancestor of the tag. Exit code zero means it is in that release:
git merge-base --is-ancestor 8303a3547509f32662be3a9cf1adc83bf41976fa 3.47.0 \
&& echo "in 3.47.0" || echo "not in 3.47.0"
The same question over the API, no clone required. A behind_by of zero means the tag already contains that commit:
gh api "repos/flutter/flutter/compare/<merge-sha>...3.47.0" \
--jq '.status, .behind_by'
5. For DevTools, resolve the pin. DevTools is not in the Flutter repo, it is pinned by revision. Read the pin at the tag, then check your commit against that revision:
gh api "repos/flutter/flutter/contents/DEPS?ref=3.47.0" --jq .content \
| base64 -d | grep dart_devtools_rev
That is how I confirmed the 3.47 pin is byte-identical to the v2.60.0 tag, and that 3.44 shipped 2.57.0. Five commands, zero trust required.
How to upgrade to Flutter 3.47, and what can break
If you are still on the 3.44 line, the upgrade itself is three commands:
flutter channel stable
flutter upgrade
flutter --version # expect: Flutter 3.47.0 · Dart 3.13.0
Then flutter clean and flutter pub get in each project so old build artifacts and pinned constraints get regenerated. If something goes sideways, flutter downgrade steps you back to the SDK you had before.
Four things in this release are worth checking before you ship on it:
- Apple minimums moved. iOS 13 to iOS 15, macOS 10.15 to macOS 12. If your app still declares an older deployment target, that is the first build failure you will meet.
- UIScene lifecycle is mandatory for UIKit apps built with Xcode 27. If you have custom AppDelegate lifecycle code, it needs the scene-based path.
- Impeller is the default renderer on desktop now, on macOS, Windows and Linux. Anything that assumed Skia behaviour, particularly custom shaders, deserves a visual pass.
-
Material and Cupertino are moving out of the SDK into the
material_uiandcupertino_uipackages. Nothing breaks today, they are opt-in, but the in-SDK libraries get formally deprecated in the November stable, so this is the release to start planning that migration.
Intel Mac support is also being phased out, which matters if any of your build machines are still on Intel silicon.
Why the boring commits are the ones I am proudest of
Look at the list again. Two small API additions, one quoting fix in a template, four doc corrections, two tool bug fixes. Not one of them is clever. Nothing in there would survive a whiteboard interview as an impressive answer.
And that is exactly the point. The doc fix on CurvedAnimation will stop leaks that would never have produced a stack trace, just a slowly degrading app. The dragUntilVisible paragraph will save some engineer forty minutes of flipping a sign back and forth at 1 AM. The Gradle template fix is in the first file of every new Android plugin project created from here on.
Framework work is not glamorous. It is compounding. A fix merged once keeps paying every time somebody runs flutter create, and I will never meet a single one of them.
If you are waiting to find something impressive enough to contribute, that instinct is what is holding you back. Read the docs of a class you use daily with the assumption that they might be wrong. Look for the parameter the wrapper widget forgot to forward. That is where merged pull requests live, and it is the same list I have been working through from Karachi for the past year.
Already merged for the next stable
Three more commits are merged and waiting on the next stable train:
- A blendMode parameter on RawImage, my first full API addition rather than a forwarded property. It took 75 days and it has its own write-up.
- Forwarding the build-name and build-number flags into desktop version.json, so Windows, macOS and Linux builds respect the same version flags the mobile targets always have.
- My name in the Flutter AUTHORS file, which is a one-line diff and the one I will be telling people about in ten years.
Counting code changes only, and the AUTHORS line is a credit rather than a fix, that is eleven merged across the Flutter ecosystem so far. The full list, with links and status, lives on my contributions page.
Next time you run flutter upgrade, spend one minute on that commits URL with your own username in it. The first time it returns a row instead of an empty list is a very good feeling.
Originally published at ishaqhassan.dev. I am a Flutter Framework Contributor with 11 merged PRs across the Flutter ecosystem, listed in the official Flutter AUTHORS file, Engineering Manager at DigitalHire, and creator of the 35-video Urdu Flutter course listed on docs.flutter.dev. Karachi, Pakistan.
Top comments (0)