Android · Hybrid apps

Reverse engineering a Flutter app: what you can actually read

Flutter changes the shape of a teardown. Most of the app's logic compiles to native code and is not coming back as readable Dart — but the packaging still tells you a lot, and the native side is where the monetization SDKs live anyway. Here is what the APK gives you, what it doesn't, and how to tell a Flutter app apart from a React Native or Unity one at a glance.

Step 1 — Identify the framework before you decompile anything

Native libraries are the most honest signal in the whole package: they survive obfuscation because they are machine code, not names. Unzip and look:

unzip -l app.apk | grep -E "lib/|flutter_assets|assets/" | head -30
You seeFramework
lib/*/libflutter.so + assets/flutter_assets/Flutter
lib/*/libreactnativejni.so, libhermes.so, index.android.bundleReact Native (Hermes or JSC)
lib/*/libunity.so, assets/bin/Data/Unity
lib/*/libmonodroid*.so, assemblies/Xamarin / .NET MAUI
lib/*/libcocos2d*.soCocos2d-x

A hybrid app can carry two of these at once — the native shell plus one embedded framework. CamScanner, for example, ships a native Android core with Flutter modules bolted on through FlutterBoost: the Flutter part runs newer features, the native part runs scanning. Miss that split and you will mis-attribute half the app's behaviour.

Step 2 — Know what the Dart snapshot does and doesn't give you

Release Flutter builds compile Dart to a native snapshot (libapp.so on Android). It is not Dart source, and it is not Smali either: there is no supported decompiler that turns it back into readable Dart. What you can do:

The boundary to state out loud: you can recover Flutter's surface (endpoints, assets, plugin set) and not its logic. Anyone claiming a full Dart decompile of a release build is selling you something.

Step 3 — The plugins are where the interesting data is

A Flutter app is a small native container plus a list of plugins, and each plugin is a normal Android dependency. That means the monetization stack is still readable in the DEX and the manifest, even though the UI logic isn't:

# The plugin list is declared in the manifest, and it maps to real SDKs
aapt2 dump xmltree app.apk --file AndroidManifest.xml | grep -iE "flutter|plugin"

# Ads / attribution / analytics live in DEX classes exactly as in a native app
jadx -d out app.apk
grep -rIl "com.google.android.gms.ads\|com.applovin\|com.facebook.ads" out/
grep -rIl "com.appsflyer\|com.adjust.sdk" out/
grep -rIl "io.flutter.plugins" out/ | head -20

For most competitive questions — how does this app make money, which networks does it use, what does it call server-side — the Flutter split costs you almost nothing, because those answers were never in the Dart code.

Step 4 — Hostnames, and why they matter more in hybrid apps

grep -rhoE "https?://[a-zA-Z0-9._-]+" out/ | sed 's|https\?://||' \
  | cut -d/ -f1 | sort | uniq -c | sort -rn | head -40

In a native app this list is mostly the vendor's own gateways. In a Flutter app you also get the Dart side's endpoints out of the snapshot strings — which is useful precisely because the Dart code is otherwise opaque. Expect the two lists to overlap; the interesting entries are the ones that only appear on one side.

Record the version code and the date next to every finding. A teardown without a version is a rumour within a month.

Want this done for a specific app? Send a Google Play link and get the full reverse-engineering report — framework split, SDK list, ad networks, API endpoints, architecture — as PDF + Markdown in about 2 hours. $29 for one app, $19 each for three or more.

Disclosure: this page is published by AppXray, which sells that report. Everything above is the method, free to use without it.