How I Reduced My Flutter APK / AAB Size — A Practical Step-by-Step Guide (with .gradle & .kts samples)

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)

  1. Build formats & commands (.aab, split APKs, analyze-size)
  2. Gradle settings: minifyEnabled, shrinkResources, ProGuard/R8, resConfigs (Groovy + Kotlin DSL)
  3. ABI splitting & signing for smaller per-device APKs
  4. Stripping debug info / obfuscation for Dart (--obfuscate, --split-debug-info)
  5. PackagingOptions and excluding useless files
  6. Asset & resource optimization (images, fonts, locales)
  7. 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 -keep rules for the offending classes (check logcat).

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 x86 and x86_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/
  • --obfuscate shortens symbol names (reduces code size slightly).
  • --split-debug-info stores 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 pngquant or zopflipng for extra compression.
  • Avoid duplicating images in drawable- densities — use drawable-nodpi or 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) or resConfigs("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=compact to 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)

  1. Remove unused packages/resources from pubspec.yaml and android/ resource folders.
  2. Convert images to WebP / compress images.
  3. Limit locales via resConfigs.
  4. Enable minifyEnabled + shrinkResources + add proguard-rules.pro.
  5. Add packagingOptions to exclude META-INF noise.
  6. Use flutter build appbundle --obfuscate --split-debug-info=./debug-info/ for production.
  7. Test with flutter build apk --split-per-abi to measure the per-ABI sizes.
  8. Use --analyze-size and inspect the DevTools size report.
  9. 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 -keep rules as needed.
  • Symbols: Keep split-debug-info files 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.

2 thoughts on “How I Reduced My Flutter APK / AAB Size — A Practical Step-by-Step Guide (with .gradle & .kts samples)”

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top