Shipping a smaller app means faster downloads, fewer uninstallations, and better user experience — especially in regions with limited bandwidth. Flutter apps can get big quickly (engine + Dart code + native libs + assets), but there are many reliable ways to shrink both .apk (per-ABI) and .aab (App Bundle) packages.
Below I’ll show practical changes you can make in your Android Gradle files (both Groovy and Kotlin DSL (.kts)), Flutter build commands, and general asset/code cleanup tips.
Quick overview (what we’ll cover)
- Build formats & commands (
.aab, split APKs, analyze-size) - Gradle settings:
minifyEnabled,shrinkResources, ProGuard/R8,resConfigs(Groovy + Kotlin DSL) - ABI splitting & signing for smaller per-device APKs
- Stripping debug info / obfuscation for Dart (
--obfuscate,--split-debug-info) - PackagingOptions and excluding useless files
- Asset & resource optimization (images, fonts, locales)
- Analyze package size and iterate
1 — Use Android App Bundle (AAB) or Split APKs
Recommendation: Use App Bundle for Play Store — Google Play will deliver optimized APKs for each device automatically. Locally or for side distribution, use --split-per-abi to produce small per-ABI APKs.
Flutter build commands:
# Build App Bundle (for Play Store)
flutter build appbundle --release
# Build per-ABI APKs (smaller single-ABI apks)
flutter build apk --release --split-per-abi
For Dart code obfuscation and symbol splitting (recommended for release):
# Build AAB with Dart obfuscation and store symbol files
flutter build appbundle --release --obfuscate --split-debug-info=./debug-info/
# Or for APK per-ABI
flutter build apk --release --split-per-abi --obfuscate --split-debug-info=./debug-info/
Note: Keep the
./debug-info/directory (upload map files to Play Console if you need stack trace deobfuscation).
2 — Enable R8 / Code Shrinking & Resource Shrinking
R8 is the code shrinker and minifier. Enable minification and resource shrinking for the release build — this strips unused Java/Kotlin classes and Android resources.
Groovy (android/app/build.gradle)
android {
compileSdkVersion 34
defaultConfig {
applicationId "com.example.yourapp"
minSdkVersion 21
targetSdkVersion 34
// Limit locales to shrink resources (optional)
resConfigs "en" // add other locales you need like "ur"
}
buildTypes {
release {
// Enables code shrinking, obfuscation and resource shrinking.
minifyEnabled true
shrinkResources true
// R8 / ProGuard config
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// signConfig signingConfig.release // your signing config
}
}
// Packaging exclusions to drop unnecessary files
packagingOptions {
exclude 'META-INF/*.kotlin_module'
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE'
}
}
Kotlin DSL (android/app/build.gradle.kts)
android {
compileSdk = 34
defaultConfig {
applicationId = "com.example.yourapp"
minSdk = 21
targetSdk = 34
// limit locales to shrink resources
resConfigs("en") // add more if needed
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
file("proguard-rules.pro")
)
}
}
packagingOptions {
exclude("META-INF/*.kotlin_module")
exclude("META-INF/DEPENDENCIES")
exclude("META-INF/LICENSE")
exclude("META-INF/LICENSE.txt")
exclude("META-INF/NOTICE")
}
}
3 — Provide ProGuard / R8 Rules (keep Flutter & plugins working)
Create android/app/proguard-rules.pro (or update your existing file) with safe rules to preserve Flutter embedding and plugin reflection usages:
# Keep Flutter embedding classes
-keep class io.flutter.embedding.** { *; }
-keep class io.flutter.plugin.** { *; }
# Keep generated plugin registrant
-keep class io.flutter.plugins.** { *; }
# Keep classes referenced by reflection (add plugin-specific rules as needed)
# Example: Keep OkHttp (if used)
-keep class okhttp3.** { *; }
# Keep annotations and public constructors used by frameworks
-keepattributes Signature
-keepattributes *Annotation*
# If you use Gson or other reflection based libs, keep model classes or add
# -keepclassmembers class com.yourpackage.models.** { *; }
If you encounter runtime errors after enabling
minifyEnabled, add-keeprules for the offending classes (checklogcat).
4 — ABI Splitting & NDK (native libs)
Native libraries (libflutter.so, plugin native libs) add size. App Bundle will auto-split ABIs on Play Store, but for APKs you can split by ABI:
Build split APKs
flutter build apk --split-per-abi --release
This produces app-arm64-v8a-release.apk, app-armeabi-v7a-release.apk, etc. Each is much smaller.
Optionally limit supported ABIs (in build.gradle)
Groovy
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
}
Kotlin DSL
defaultConfig {
ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
}
}
Only include the ABIs you need. If you remove
x86andx86_64, you lose support for emulators but save size.
5 — Dart-level size reductions (tree shaking, obfuscation)
Flutter already tree-shakes unused Dart code in release mode, but you can further obfuscate and split debug info:
flutter build appbundle --release --obfuscate --split-debug-info=./debug-info/
--obfuscateshortens symbol names (reduces code size slightly).--split-debug-infostores symbol map files outside the binary (reduces app size, required to deobfuscate stack traces).
Keep the debug-info/ folder safe — upload relevant symbol files to Play Console when you publish updates.
6 — Images, fonts, resources — Optimize aggressively
These are often the largest contributors to APK size.
Image tips
- Convert PNGs to WebP (lossy or lossless) — saves a lot.
- Use vector drawables (SVG → VectorDrawable) for simple icons.
- Use
pngquantorzopflipngfor extra compression. - Avoid duplicating images in
drawable-densities — usedrawable-nodpior provide only necessary densities.
Example commands
# Convert to webp (lossy)
cwebp -q 80 image.png -o image.webp
# Lossless PNG optimization
pngquant --quality=60-80 --speed=1 image.png --output image.png
Fonts
- Only include the font weights you use (don’t include entire font families).
- Prefer system fonts where possible.
Locales
- Limit Android resource locales so Play can strip unused locales:
resConfigs "en", "ur"(Groovy) orresConfigs("en", "ur")(kts)
7 — PackagingOptions & Exclusions
Exclude metadata and unused files that get bundled from dependencies:
Groovy
android {
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE*'
exclude 'META-INF/NOTICE*'
exclude 'META-INF/*.kotlin_module'
}
}
Kotlin DSL
android {
packagingOptions {
exclude("META-INF/DEPENDENCIES")
exclude("META-INF/LICENSE*")
exclude("META-INF/NOTICE*")
exclude("META-INF/*.kotlin_module")
}
}
8 — Analyze APK / AAB size and hotspots
Use Flutter’s analyze-size tooling:
# Generate size analysis (creates JSON and flame chart)
flutter build apk --release --analyze-size
# or
flutter build appbundle --release --analyze-size
After build you get a .json file in build/ and Flutter will output a link to open the size analysis in DevTools (shows Dart code, native libs, assets breakdown). Use that to find the biggest contributors.
9 — Extra advanced tips
- Lazy-load large assets from network instead of bundling them.
- Split feature modules (Android dynamic feature modules) — advanced: allows Play Store to deliver features on demand.
- Avoid large reflection-based libraries (remove unused SDKs).
- Use
flutter build ipa’s bitcode options for iOS (not covered here). - Strip native debug symbols if you don’t need them in release; R8 + NDK settings can help. (Be careful: you’ll lose readable native stack traces.)
- Use
flutter pub deps -- --style=compactto inspect packages and remove unused heavy packages.
10 — Example: Complete android/app/build.gradle (Groovy)
apply plugin: 'com.android.application'
android {
compileSdkVersion 34
defaultConfig {
applicationId "com.example.yourapp"
minSdkVersion 21
targetSdkVersion 34
versionCode 1
versionName "1.0"
// restrict locales
resConfigs "en"
// optionally set ABI filters (if you want to produce only some ABIs)
// ndk { abiFilters 'arm64-v8a', 'armeabi-v7a' }
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
packagingOptions {
exclude 'META-INF/*.kotlin_module'
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
11 — Example: Complete android/app/build.gradle.kts (Kotlin DSL)
plugins {
id("com.android.application")
kotlin("android")
}
android {
compileSdk = 34
defaultConfig {
applicationId = "com.example.yourapp"
minSdk = 21
targetSdk = 34
versionCode = 1
versionName = "1.0"
resConfigs("en")
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
file("proguard-rules.pro")
)
// signingConfig = signingConfigs.getByName("release")
}
}
packagingOptions {
exclude("META-INF/*.kotlin_module")
exclude("META-INF/DEPENDENCIES")
exclude("META-INF/LICENSE")
exclude("META-INF/LICENSE.txt")
exclude("META-INF/NOTICE")
}
}
Practical checklist (apply in this order)
- Remove unused packages/resources from
pubspec.yamlandandroid/resource folders. - Convert images to WebP / compress images.
- Limit locales via
resConfigs. - Enable
minifyEnabled+shrinkResources+ addproguard-rules.pro. - Add
packagingOptionsto exclude META-INF noise. - Use
flutter build appbundle --obfuscate --split-debug-info=./debug-info/for production. - Test with
flutter build apk --split-per-abito measure the per-ABI sizes. - Use
--analyze-sizeand inspect the DevTools size report. - Iterate: add keep rules only when you hit runtime errors.
Example Commands Summary
# 1) Build App Bundle for Play Store with debug-info
flutter build appbundle --release --obfuscate --split-debug-info=./debug-info/
# 2) Build split APKs for direct distribution
flutter build apk --release --split-per-abi --analyze-size
# 3) Analyze a produced size-report (DevTools) — Flutter will show a link after build
Final Notes & Cautions
- Testing: After enabling minify/obfuscate, thoroughly test the release build — reflection and plugin initialization problems are the most common runtime issues. Fix by adding
-keeprules as needed. - Symbols: Keep
split-debug-infofiles safe — you need them for deobfuscation when you get obfuscated crash stacks. - Play Store: Use AAB for Play Store — Play optimizes per-user APKs (no need to ship multiple ABIs).
- Balance: Don’t overly cripple debugging; keep a copy of signing keys and symbol maps for updates.
some truly interesting points you have written.
thanks