AppInspect
On this page
Getting started

Builds and environments

AppInspect never sees your Gradle variant names. staging, qa, uat, preprod, nightly — all the same to the library. It decides whether to run from two inputs only, which is why the answer to “will it work in my environment?” is short once you know what they are.

Two inputs, three tiers

The first input is FLAG_DEBUGGABLE, which the Android Gradle plugin sets from your build type's isDebuggable. The second is appinspect_enabled_in_non_debuggable_build, a boolean resource you may declare for one variant. Together they resolve to a tier.

FLAG_DEBUGGABLEOpt-in resourceTierWhat happens
setnot readDEBUGEverything enabled.
cleartrueSTAGINGEverything enabled.
clearfalse or absent (the default)PRODUCTIONEverything disabled. Nothing captured, nothing written, no way in.

Note what the third row means in practice: the safe outcome is the one you get by doing nothing. A build has to be told to run the inspector, never told to stop.

Your setup, and what happens in it

Your setupTierResultWhat you have to do
debugImplementation of the full library DEBUG Works fully Nothing.
Custom variant, isDebuggable = true DEBUG Works fully Nothing. The resource is never even read.
Custom variant, isDebuggable = false PRODUCTION Dead — the inspector will not open Add the opt-in resource, or make the variant debuggable.
The same, plus the opt-in resource STAGING Works fully Done.
releaseImplementation of appinspect-no-op Inert by design; no inspection code in the APK Nothing. This is the recommended release setup.
Full library in release by mistake, no flags PRODUCTION Inert. Nothing breaks and nothing is captured Still switch release to the no-op.
Full library in release with the flag set STAGING It runs, in production Don't. See the one way to shoot yourself.
Product flavours rather than build types Follows the resolved variant's isDebuggable Works Put the resource in src/<flavour>/res/.
A minified, R8'd, resource-shrunk non-debug variant As above Works fully Nothing — see minification.

The only decision you have to make

Is your non-debug variant isDebuggable? Open the build type and look, remembering that initWith copies the value it inherits:

app/build.gradle.kts
create("staging") {
    initWith(getByName("release"))   // inherits isDebuggable = false
    isDebuggable = true              // ← if this line is here, you are done
}

That pattern is common: teams inherit minification and signing from release, then turn debuggability back on so the build stays attachable to a debugger and to Studio's profilers. If your variant looks like that, AppInspect already works there with no configuration and nothing on this page applies to you.

Turning it on in a non-debuggable build

Only if isDebuggable stays false do you need the opt-in. Two equivalent ways — pick whichever fits how your project is organised.

A — one line in the build type, any variant name
create("uat") {
    initWith(getByName("release"))
    matchingFallbacks += listOf("release", "debug")
    resValue("bool", "appinspect_enabled_in_non_debuggable_build", "true")
    resValue("string", "appinspect_environment_name", "uat")   // optional label
}
B — app/src/uat/res/values/appinspect.xml
<resources>
    <bool name="appinspect_enabled_in_non_debuggable_build">true</bool>
</resources>

Either one resolves that variant to STAGING with every feature enabled and no host code at all. appinspect_environment_name is optional and only labels the environment in the Runtime panel — useful precisely because the library cannot see that you called the variant uat.

matchingFallbacks is not an AppInspect thing

Any custom build type needs it, because AppInspect publishes only debug and release variants. Leave it out and Gradle fails with “No matching variant of io.github.suryansh1720001.appinspect:appinspect was found” before the library gets a say in anything.

Why a resource and not a configuration flag

Two constraints, and nothing else satisfies both. Timing: AppInspect installs itself from AndroidX Startup, which runs in a ContentProvider before your Application.onCreate() — anything written in Kotlin arrives after the enablement decision has been made, but a resource is readable at that moment. Non-leakage: Android never merges one variant's resources into another, so a value declared in uat is physically absent from the release APK, which then gets the library's false default. A BuildConfig field or a manifest placeholder cannot promise that.

It also cannot be inferred. At runtime a non-debuggable staging build and a release build are byte-for-byte identical, and the fact that you scoped the dependency to uatImplementation is Gradle information that does not survive into the APK.

If you need to test on a release-like build

This comes up on every team: something only reproduces in a signed, minified, production-shaped build, and you want the inspector there for an afternoon. Do it with a variant, not with your release build.

  1. Create a variant that initWith(release), so it keeps release's minification, shrinking and signing config, and give it matchingFallbacks.
  2. Either set isDebuggable = true on it, or add the opt-in resource to it. Both give you the full inspector; the resource keeps the build non-debuggable, which is closer to production.
  3. Point the dependency at that variant: add("stagingImplementation", …). Leave release on the no-op.
  4. Turn mocking off for that build — allowResponseMocking = false — if it will ever point at production data. A build that can rewrite live responses is a support incident waiting to happen.

What you must not do is put the flag in release itself or in src/main/res/, which applies to every variant. That is the one path that ends with the inspector in front of real users.

The one way to shoot yourself

Write the opt-in into your release build type, or into src/main/res/, while the full library is on the release classpath, and the inspector will run in production. That is intentional and unavoidable: it is an explicit instruction, in your own build script, in a line a reviewer can see — exactly the trust level of typing implementation instead of debugImplementation. No library can defend against its owner deliberately enabling it.

Two rules make it unreachable in practice. Never put the resource in src/main/res/ — use a build-type resValue or a variant source set. And keep releaseImplementation(appinspect-no-op): then there is no inspection code in the APK for any flag to enable, whatever the flag says.

If it does happen, everything becomes active at once — capture writes real user traffic including Authorization headers to the device, the crash handler installs, the launcher shortcut publishes, notifications post, Logcat capture becomes reachable, and response mocking can serve canned data to real users. The security model spells out what to switch off first, and note that the resource still cannot reach the PRODUCTION tier: that additionally requires allowInProductionBuilds = true, which is host Kotlin code that will not even compile against the no-op.

Minification, R8 and resource shrinking

You add nothing. AppInspect ships its own consumerProguardFiles, which Gradle applies to your app automatically, so a variant created with initWith(getByName("release")) inherits isMinifyEnabled and isShrinkResources and works unchanged.

The rules exist because the API layer reaches its storage, core and UI modules through Class.forName — that is what lets any one of them be absent. Every lookup has a graceful fallback, which is exactly why the keep rules ship with the library rather than being left to you: a missing rule would not crash, it would degrade quietly. The storage bridge going missing would drop you onto an in-memory repository, so capture still works but nothing survives a restart; the core bridge going missing would empty the Runtime panel's App, Device and Session sections. Both read as feature bugs rather than build-configuration bugs.

The opt-in flag survives the resource shrinker too, because it is read as a typed R.bool reference the shrinker can see rather than through Resources.getIdentifier, which would have needed a tools:keep rule in your app.

Where configuration fits

Everything above is build-time and decides whether AppInspect runs. Everything on the configuration reference is runtime and decides what it does once it is running — which panels appear, what a tester can edit, what is masked. The two are independent, with one connection worth knowing: AppInspect.install() replaces the whole configuration, which discards the tier AppInspect inferred for the build, including a staging variant's resource opt-in. Prefer updateConfiguration, which keeps it.