Flutter apps are fat by default. A fresh flutter create spits out a release APK north of 22 MB and an IPA north of 28 MB before you've written a line of code. Ship without thinking about size and you're handing users a 50 MB download that drops installs, especially in emerging markets. This is the 15-item checklist I run on every Flutter release. Followed end to end it cuts typical Flutter app size by 45 to 60 percent.
Short version: measure first, tree-shake everything, split ABIs, subset fonts, compress assets, strip iOS debug symbols, and audit dependencies. Each step is small; the compound effect is massive. Real example at the bottom: my habit-tracker template went from a 24 MB APK to an 11 MB APK following this list.
Why Flutter apps are bloated by default
Flutter ships the Dart VM, Skia or Impeller graphics, ICU internationalization data, Material and Cupertino widget libraries, and your app code, all inside a single binary. On Android you also get four ABI variants (arm64-v8a, armeabi-v7a, x86_64, x86) packed into one APK unless you tell the build system otherwise. On iOS you inherit Swift runtime plus your Dart snapshot plus bitcode stubs. The total adds up quickly.
The optimizations below are roughly in order of impact. Work the top of the list first.
1. Measure before you optimize
Every decision after this step is easier when you have numbers. Flutter ships a build-size analyzer that writes a JSON report and opens a visual tree explorer.
flutter build apk --analyze-size --target-platform android-arm64
flutter build ios --analyze-sizeThe command exits with the path to a .json report. Load it atdevtools.flutter.dev/appsize for a treemap. You will discover specific packages that dominate: font_awesome_flutter burns 2 MB, a single unused SVG pack burns 800 KB, a locale-heavy intl data file hits 1.4 MB. Baseline first, then optimize.
2. Enable tree shaking and release mode (obvious but often skipped)
Flutter's release builds already tree-shake Dart code and icons, but only when you actually build release. In CI I still see teams shipping profile or debug builds to Play Console because someone tested with flutter build apk without flags.
- Always
--releasefor Play Store / App Store. - Set
--tree-shake-icons(default on release, but double-check your CI). - Enable
--split-debug-info=./symbolsto strip debug info from the binary while keeping symbol files for crash reports. - Add
--obfuscateif your Dart code contains proprietary logic.
3. Ship Android App Bundles, not APKs
Play Console has required AAB for new apps since 2021 but some teams still upload a universal APK for testing channels. AAB is 25 to 35 percent smaller at install time because Play slices it into device-specific APKs.
flutter build appbundle --release \
--split-debug-info=./symbols \
--tree-shake-icons4. ABI splits save 40 to 60 percent on Android
If you still need APKs (sideload distribution, alternative stores), split them by ABI. A single architecture APK is typically 40 percent the size of a universal one.
// android/app/build.gradle
android {
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86_64'
universalApk false
}
}
}Do not ship x86 unless you target Chromebook or specific Android emulators. Almost all real devices are arm64-v8a.
5. Subset fonts (this is where most Flutter apps bleed)
The default google_fonts package loads entire font families at runtime, which is great in debug but costs MBs in production. Two options:
- Pre-bundle only the weights you use. Download the font files, include them in
pubspec.yaml, and reference them by name. Typical savings: 500 KB to 2 MB. - Use
fonttoolsto subset the glyph set. If your app only renders Latin characters, strip CJK, Cyrillic, and symbol ranges. Can cut a font from 800 KB to 80 KB per weight.
Icon fonts are worse. font_awesome_flutter is 2 MB on disk. Flutter's built-in--tree-shake-icons helps, but only for Icons.* usage. If you use FontAwesome you are still shipping the whole font unless you subset it manually.
6. Compress assets: WebP, AVIF, and SVG
| Asset type | Default in Flutter | Replace with | Typical saving |
|---|---|---|---|
| PNG images (non-photo) | PNG | WebP lossless or SVG | 60 to 80 percent |
| PNG photos | PNG | WebP lossy (quality 85) | 70 to 85 percent |
| JPG photos | JPG | WebP or AVIF | 30 to 50 percent |
| Icons | PNG at 3x | SVG via flutter_svg | 90 percent plus crispness |
| Animations | GIF or video | Rive or Lottie | 50 to 90 percent |
WebP has been supported in Flutter since 3.7. Run every PNG and JPG through cwebpin your build pipeline. One of my apps saved 6 MB by converting the onboarding illustration pack from PNG to WebP.
7. R8 and ProGuard on Android
R8 replaced ProGuard as the default Android shrinker in AGP 3.4+. Flutter release builds already enable it, but you control the rules via proguard-rules.pro. Two quick wins:
- Add
minifyEnabled trueandshrinkResources trueto your release build type if they are not already set. - Avoid
-keep class **wildcard rules that disable minification for whole packages. Only keep the specific classes your reflection or platform channels need.
8. iOS: strip symbols and bitcode
iOS IPAs bundle LLVM bitcode by default and keep debug symbols until you tell Xcode to strip. Apple stopped requiring bitcode in Xcode 14+. Turning it off saves 15 to 25 percent on IPA size.
- Xcode → Build Settings →
Enable Bitcode→ No. - Xcode → Build Settings →
Strip Debug Symbols During Copy→ Yes. - Xcode → Build Settings →
Deployment Postprocessing→ Yes. - Xcode → Build Settings →
Strip Style→ All Symbols.
Set these in the Release configuration only so debug builds keep their symbols.
9. Kill unused locales
Flutter bundles ICU locale data for every supported language. If you only ship to English and Spanish markets, you can safely strip the rest. Edit your MaterialApp to declare only the locales you need, then tree-shaking cuts the data files automatically on iOS and Android.
10. Audit your dependency tree
Every package you add has a size cost. Run flutter pub deps and cross-reference with the build-size report. Common bloat culprits:
firebase_crashlyticsadds 1.8 MB. Worth it for production.google_maps_flutteradds 2.5 MB. Only include on screens that need it.video_playerwithchewieadds 3 MB. Consider a web-view for rare video cases.share_plus,url_launcher,package_info_plus: thin, keep.- Analytics SDKs with session replay: 1 to 3 MB depending on provider.
11. Dynamic feature modules (Android only)
Play App Bundles support dynamic feature modules that download on demand. If you have a video player or an AR experience that 10 percent of users use, make it a dynamic feature. This pattern is underused in Flutter because it requires a Kotlin wrapper, but the savings are real for feature-rich apps.
12. Remove unused debug tools
Packages you forgot to remove from dependencies:
loggeroutput in release: strip via wrapping inkReleaseMode.flutter_inspector: dev-only.flutter_test: should be indev_dependencies.- Leftover example assets from
flutter_animate,rivetutorials.
13. Use flutter_launcher_icons smartly
flutter_launcher_icons generates every icon size by default. If you only target iOS or only target Android, skip the other set in the config. Savings: 200 to 400 KB.
14. Profile mode before release (catch bloat early)
Run flutter build apk --profile --analyze-size as part of your PR CI pipeline. Profile builds are only 5 to 10 percent larger than release and catch size regressions before they merge. Configure a threshold: fail CI if APK grows more than 500 KB in a single PR.
15. Before and after: a real case study
Here is the size journey for a habit tracker app built on Flutter Kit, starting from the default and applying every optimization above.
| Step | APK size | Change |
|---|---|---|
| Fresh Flutter Kit clone, universal APK, debug fonts | 24.1 MB | baseline |
| Switch to App Bundle | 17.8 MB | -26 percent |
| ABI split (arm64 only) | 13.4 MB | -25 percent |
| Subset fonts + kill FontAwesome | 12.1 MB | -10 percent |
| WebP assets, SVG icons | 11.4 MB | -6 percent |
| R8 tuning, strip symbols | 11.1 MB | -3 percent |
| Final | 11.1 MB | -54 percent total |
The first three steps (bundle, ABI, fonts) do 80 percent of the work. The rest is polish. If you have one afternoon, do those three.
Common mistakes that re-bloat your app
- Adding
flutter_local_notificationsand its 1.2 MB ICU dep without realizing it bundles a second locale database. - Using
cached_network_imagewith default cache settings (300 MB on-device cache, not a binary size issue but a real storage issue). - Importing the whole
lodash-equivalent Dart utility package for one function. - Letting
generated_plugin_registrant.dartstay after removing a plugin. - Shipping multiple PNG densities when you already have a WebP fallback chain.
- Including animation JSON files that are copies of a Figma export, not optimized Lottie output.
What The Flutter Kit ships
The Flutter Kit ships with the optimizations in this guide already applied. AAB build config, ABI split with arm64 default, WebP asset pipeline, subsetted font files, production-tuned R8 rules, and the iOS Build Settings configured for size. A typical Flutter Kit release APK lands at 12 to 14 MB before you add your own assets.
$69 one-time, unlimited commercial projects. See every integration on the features page or jump to checkout.
Final checklist (print this before your next release)
- Measured baseline with
--analyze-size - Release build with tree shaking and icon shaking enabled
- Android App Bundle, not universal APK
- ABI split configured for arm64 + armeabi-v7a only
- Fonts subsetted or pre-bundled (no
google_fontsruntime on release) - Images converted to WebP or SVG
- R8 minify and shrink resources enabled
- iOS bitcode disabled, symbols stripped
- Unused locales removed
- Dependency tree audited against size report
- CI size-regression gate in place