Code Obfuscation
3 min read
Build & Security
Obfuscation is not encryption — a determined attacker can still inspect the binary — but it removes the cheap "open in a decompiler and read source-like names" attack. Always pair --obfuscate with --split-debug-info or crash reports become unreadable.
| What obfuscation does | What it doesn't do | |
|---|---|---|
| Renaming | ✅ Class/method/field names → opaque | ❌ Encrypt logic |
| Stack traces | Become unreadable without the mapping file | (use --split-debug-info) |
| Asset / string protection | ❌ — strings (URLs, keys) remain visible | Use additional obfuscation for those |
| Anti-reverse-engineering | Raises the bar; doesn't eliminate the threat | Determined attackers still get in |
| Performance | No runtime cost | n/a |
Combined with tree shaking (Q47), obfuscation shrinks the binary and obscures what's left.
Code in action
# Build with obfuscation + debug-info splitting
flutter build apk --obfuscate --split-debug-info=./debug-info/android
# Same for iOS
flutter build ipa --obfuscate --split-debug-info=./debug-info/ios
# What lands where:
# APK / IPA → user binary (obfuscated, no readable symbols)
# ./debug-info/ → mapping files needed to symbolicate crashes
# When a crash arrives with an obfuscated stack:
flutter symbolize \
-i crash.txt \
-d ./debug-info/android/app.android-arm64.symbols
When to obfuscate
| Situation | Obfuscate? |
|---|---|
| Production Flutter app | ✅ Default |
| Internal company app on a managed fleet | Optional |
| Open-source app (the code is public anyway) | ❌ Doesn't help |
| App with on-device secrets | ✅ But don't rely on it for secrets — move them server-side |
| Building for the Play Store / App Store | ✅ Recommended (Play Store also reprocesses with R8) |
| Debug / development builds | ❌ Hurts iteration |
Operational checklist
| Step | Why |
|---|---|
Always pass --split-debug-info with --obfuscate | Without mapping, crashes are gibberish |
| Version the mapping file per release | Crashes report against a specific build |
| Back up debug-info to a safe place (private git, S3) | Lose it = unsymbolicatable crashes forever |
| Upload to your crash service (Crashlytics, Sentry) | Auto-symbolication on incoming reports |
| Test that crash reporting decodes correctly | Make a deliberate crash in a release build, verify the stack |
| Don't ship the debug-info folder inside the binary | Defeats the obfuscation |
Common mistakes to avoid
❌ Obfuscating without --split-debug-info
Crash reports become impossible to read. The next outage = chaos.
❌ Losing the debug-info folder
No way to recover. Treat it like build artifacts that MUST be archived.
❌ Believing obfuscation hides secrets
Hard-coded API keys are still extractable. Move secrets server-side.
❌ Mixing builds with and without --obfuscate in the same release
Crash reports lose consistency. Pick one.
❌ Trying to obfuscate non-release builds
Debug/profile builds need readable symbols. Only release.
Interview follow-ups
-
What's the difference between obfuscation and minification? Minification just shortens identifiers and removes whitespace — typical JavaScript optimisation. Obfuscation in Flutter's sense also rewrites symbols to non-meaningful opaque names. Flutter's
--obfuscateis the latter; it happens during AOT compilation alongside tree shaking. -
Does obfuscation slow the app down? No. Renaming happens at compile time; runtime behaviour is identical. Tree shaking + obfuscation can actually shrink the binary, leading to faster startup and smaller install size.
-
What's in the debug-info folder and why must you keep it? It's a mapping from obfuscated symbols back to the original names, per build/architecture. Without it, crash stack traces look like
_a.b.c$d— meaningless. Your crash reporter uses it (orflutter symbolize) to decode incoming reports. -
Can obfuscation protect API keys baked into the app? Not really. Strings are still extractable —
strings binary.so | grep -i tokenfinds them. Treat any value shipped in the binary as effectively public; move sensitive operations server-side or restrict keys by bundle ID / signature on the provider side.
How helpful was this content?
Please sign in to rate this article.