← Articles

Obfuscating Flutter apps for release

By Charlin Joe · 13 November 2025

Obfuscation renames Dart classes, methods, and fields in the compiled binary to meaningless strings, making reverse engineering significantly harder. It's an important step for production Flutter apps, especially those handling payments or sensitive data.

What obfuscation does

Without obfuscation, the Dart snapshot contains readable names:

OrderRepository.createOrder()
CartBloc._calculateTotal()
PaymentService.processCard()

With obfuscation:

a.b()
c._d()
e.f()

This slows down (but does not prevent) reverse engineering. It's one layer in a defense-in-depth strategy.

Enabling obfuscation

# Android
flutter build apk \
  --obfuscate \
  --split-debug-info=./debug-info/android

flutter build appbundle \
  --obfuscate \
  --split-debug-info=./debug-info/android

# iOS
flutter build ipa \
  --obfuscate \
  --split-debug-info=./debug-info/ios

# Combined in a script:
BUILD_DIR="./debug-info/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BUILD_DIR"

flutter build appbundle --obfuscate --split-debug-info="$BUILD_DIR/android"
flutter build ipa --obfuscate --split-debug-info="$BUILD_DIR/ios"

The debug-info folder

The --split-debug-info directory contains the symbol mapping needed to understand obfuscated crash reports. Store this securely. You need it to:

  • Symbolicate crashes from Crashlytics
  • Decode stack traces from Sentry
  • Debug production issues

Never commit this folder to your main repository. Store in a separate secure location or upload to your crash reporting service.

Symbolicating crash reports

Obfuscated crash stacks look like:

#00 abs 0x000000000063c54e _kDartIsolateSnapshotInstructions+0x6c554e
#01 abs 0x0000000000595e13 _kDartIsolateSnapshotInstructions+0x555e13

To symbolicate:

# With flutter tools
flutter symbolize \
  --debug-info=./debug-info/android/app.android-arm64.symbols \
  --input=./crash.txt

# Output:
# #00 OrderRepository.createOrder (package:myapp/data/repositories/order_repository.dart:42)

Sentry integration

Sentry can symbolicate automatically if you upload debug symbols:

npm install -g @sentry/cli

# Upload Android symbols
sentry-cli upload-dif ./debug-info/android \
  --org your-org \
  --project your-project

# Upload iOS dSYM
sentry-cli upload-dif ./debug-info/ios \
  --org your-org \
  --project your-project

In CI:

- name: Build with obfuscation
  run: |
    flutter build appbundle \
      --obfuscate \
      --split-debug-info=./debug-info

- name: Upload symbols to Sentry
  run: |
    sentry-cli upload-dif ./debug-info \
      --org ${{ secrets.SENTRY_ORG }} \
      --project ${{ secrets.SENTRY_PROJECT }}
  env:
    SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}

Firebase Crashlytics

Crashlytics automatically symbolicate if you include the Firebase Crashlytics Gradle plugin:

// android/app/build.gradle.kts
plugins {
  id("com.google.firebase.crashlytics")
}

The plugin uploads debug symbols automatically on each build.

Android ProGuard

For additional Android-level obfuscation (beyond Dart obfuscation), enable R8:

// android/app/build.gradle.kts
buildTypes {
  release {
    isMinifyEnabled = true
    isShrinkResources = true
    proguardFiles(
      getDefaultProguardFile("proguard-android-optimize.txt"),
      "proguard-rules.pro"
    )
  }
}

Add Flutter-specific ProGuard rules:

# proguard-rules.pro
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }

Limitations

Obfuscation does NOT prevent:

  • Decompiling the app binary
  • Extracting hardcoded strings (API keys, URLs)
  • Dynamic analysis (running the app in a debugger or emulator)
  • Observing network traffic

For secrets, use dart-define from CI secrets rather than hardcoding in source. For API protection, enforce authorization on your backend.

Common pitfalls

Not saving the debug-info folder. If you lose the symbol files, production crash reports become unreadable. Archive them alongside each release.

Obfuscating debug builds. Obfuscation makes debugging impossible — don't apply it to debug or profile builds.

Using --split-debug-info without --obfuscate. You can use --split-debug-info alone (just splits symbols without renaming) but don't confuse this with obfuscation. Both flags together provide the full benefit.

Sign in to like, dislike, or report.

Obfuscating Flutter apps for release — ANN Tech