Skip to content
ExponaBLOG
FLUTTER · DAY 083 / 100

Reduce app size: what every Flutter developer should know

Smaller apps get more installs.

Expona Admin6 min read

Reduce app size

I've watched users abandon apps in the Play Store before they even download them, simply because the file was too large. When your Flutter app sits at 150 MB, you're losing installs to users on metered connections, older devices with limited storage, and people in markets where data costs matter. This is Day 83 of my 100-day Flutter series, and today we're tackling one of the most underrated performance levers: keeping your app binary lean.

Why app size matters in real production

Before diving into technique, let me be direct: app size is a conversion metric. Google Play tracks install abandonment by size tier. Android developers I've worked with have seen 15–30% increases in installs just by cutting their APK from 80 MB to 45 MB. Users on 4G in emerging markets or anyone on a 200 MB app limit per download will skip you entirely.

From a business perspective, smaller apps also mean faster CI/CD pipelines, quicker over-the-air updates, and reduced infrastructure costs for distribution. From a technical standpoint, a lean binary is a sign of discipline—it forces you to audit dependencies, eliminate dead code, and compress assets intentionally.

App bundles on Android

The single biggest win for most Flutter teams is switching from APK to Android App Bundle (AAB) format. I've seen this alone cut deliverable size by 30–40% on first install.

When you build an APK, you're creating one binary that includes resources for every screen density, every language, every architecture. Most users need only a fraction of that. App Bundles let Google Play generate optimized APKs per device, delivering only what each user actually needs.

In Flutter, building an App Bundle is straightforward:

dart
flutter build appbundle --release

This creates a .aab file under build/app/outputs/bundle/release/. Upload this to Play Console, and Google handles the rest. On Google Play, I've seen first-install sizes drop from 85 MB to 35 MB just from this step alone.

For local testing, use bundletool to generate split APKs and inspect what each device type would receive:

dart
bundletool build-apks \
  --bundle=app.aab \
  --output=app.apks \
  --mode=universal

This universal APK is larger (closer to the original), but it shows you the upper bound. Device-specific splits will be significantly smaller.

One practical note: if you're still shipping raw APKs to customers or internal testers, ask yourself why. App Bundles are the modern standard. The only reason to stick with APKs is if you're distributing outside Google Play—and even then, consider alternatives like Firebase App Distribution, which handles optimization for you.

Analyze size with --analyze-size

You can't optimize what you don't measure. Flutter's --analyze-size flag gives you a breakdown of what's actually in your binary.

dart
flutter build appbundle --release --analyze-size

This generates a JSON file with a detailed size report. I ran this on a production e-commerce app I worked on, and it revealed that a single dependency—an image manipulation library we'd added months ago for one feature—accounted for 12 MB. We'd removed the feature but never cleaned up the package. The tool pays for itself immediately.

The output breaks down by Dart code, native code, resources, and more. For a real example, here's what I typically see:

Category Size (MB) Percentage
Dart code 8.2 18%
Native code (arm64) 22.1 48%
Resources (images, fonts, strings) 14.5 31%
Miscellaneous 1.2 3%

Native code is often the largest culprit. This is where plugins and their dependencies live. If you're using Firebase, permissions, camera, payments, or video libraries, each one bundles native Android and iOS SDKs. This is unavoidable in many cases, but it's where you should focus if you're over budget.

I recommend running this analysis quarterly and comparing trends. If size creeps up 5 MB month-over-month, that's a signal to audit recent dependency changes.

Compress and resize assets

Images, videos, fonts, and other binary assets often represent 25–40% of app size. This is low-hanging fruit.

Image optimization: Never ship images at their source resolution. A 3000×2000 photo from a designer's Figma export doesn't belong in a mobile app. Use tools like ImageMagick or online services to resize images to the largest size you'll display them, then compress aggressively.

dart
# Resize and compress
magick input.png -resize 1200x800 -quality 75 output.jpg

JPEG for photographs, WebP for general assets. PNG only when you need lossless quality or transparency. In a travel app I worked on, switching product images from PNG to WebP cut the assets folder from 24 MB to 6 MB. Users noticed no quality difference.

Remove duplicate assets: Run a quick audit of your assets/ folder. I've found unused or duplicate images multiple times. A script can help:

dart
find assets/ -type f | sort | uniq -d

Font subsetting: If you're using custom fonts, you probably don't need all 2,000+ glyphs. Use fonttools to subset fonts to only the characters you actually use:

dart
pip install fonttools
pyftsubset MyFont.ttf --unicodes=0x20-0x7E,0x00E9 --output-file=MyFont-subset.ttf

A full Roboto font is roughly 1 MB. If you only need ASCII plus a few accented characters, you can get it under 200 KB.

Compress video assets: If you're embedding video (splash screens, onboarding), use H.264 at modest bitrates. A 10-second 1080p intro video can be 2–5 MB if compressed properly, or 50+ MB if not.

Store these optimized assets in version control, but consider keeping originals in a separate location (Google Drive, S3) for future edits. This keeps the repo lean.

Remove unused packages

Every dependency you add carries a cost. Some are worth it; others aren't.

I've joined teams with bloated pubspec.yaml files full of packages that were tested once, abandoned, or left over from a feature that was removed years ago. Running flutter pub deps gives you visibility, but you need discipline to act on it.

Start by auditing your direct dependencies. Ask for each one:

  • Is this actively used in the current codebase?
  • Is there a lighter alternative?
  • Can I replace it with platform channels or native code for a specific use case?

For example, in a CRM app, I found we were using intl for date formatting but only for one screen. Swapping to simple DateFormat from `dart:core` cut the dependency entirely.

If a package is large but necessary (like Firebase), consider lazy-loading features. If you don't need Firebase Analytics on every app startup, don't import it globally:

dart
// Bad: imported everywhere
import 'package:firebase_analytics/firebase_analytics.dart';

// Better: lazy load only where needed
Future<void> trackEvent() async {
  final analytics = (await import('firebase_analytics')).FirebaseAnalytics.instance;
  // track
}

This won't work directly in Dart (it's not JavaScript), but the principle applies: defer heavy dependencies to where they're actually used.

For Flutter specifically, you can organize your project so analytics or optional plugins are in separate feature modules, loaded conditionally. This requires build-time configuration but pays off on size-sensitive markets.

Run flutter pub deps --style=compact to see transitive dependencies. I've found that a single package sometimes pulls in 5–10 indirect dependencies, some of which are redundant. Check if newer versions of your packages have reduced dependency trees.

Common mistakes

Mistake 1: Ignoring native dependencies. A Flutter package like video_player bundles the entire Android ExoPlayer library. You can't optimize this away easily without rewriting the plugin. Know which packages are heavy and evaluate trade-offs early.

Mistake 2: Not using App Bundles in production. Some teams still upload APKs to Google Play out of habit or misunderstanding. You're leaving 30–40% of optimization on the table. Switch to AAB format now.

Mistake 3: Resizing assets manually and inconsistently. Set up a pre-commit hook or CI step to enforce asset size limits. I've seen teams optimize assets once, then new designers add 50 MB of uncompressed images six months later.

Mistake 4: Optimizing too late. Size creep happens gradually. By the time your app is 200 MB, you're paying in lost conversions and developer time to untangle it. Make size a metric from Day 1, just like frame rate or crash rate.

Key takeaways

  • Use Android App Bundles (AAB) for Google Play; expect 30–40% size reduction on first install compared to universal APKs.
  • Run flutter build appbundle --release --analyze-size regularly to identify bloat by category.
  • Compress and resize all assets before committing them; automate this in CI if possible.
  • Audit your pubspec.yaml quarterly; remove unused packages and prefer lighter alternatives where available.
  • Track app size as a production metric; set targets (e.g., under 50 MB for first install) and monitor trends.
  • Native code (from plugins) is often the largest component; evaluate plugin dependencies during architecture decisions.
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