- Android API 26+ (Android 8.0)
- Kotlin 1.9+
- Java 17
Add the library as a debugImplementation dependency so it is excluded from release builds entirely:
// settings.gradle.kts -- include the library
includeBuild("path/to/AppReveal/Android") {
dependencySubstitution {
substitute(module("com.appreveal:appreveal")).using(project(":appreveal"))
substitute(module("com.appreveal:appreveal-noop")).using(project(":appreveal-noop"))
}
}
// app/build.gradle.kts
dependencies {
debugImplementation("com.appreveal:appreveal")
releaseImplementation("com.appreveal:appreveal-noop") // no-op stub for release
}The appreveal-noop module provides an empty AppReveal.start() stub so you don't need BuildConfig.DEBUG checks in your code (though they're recommended as a safety net).
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
AppReveal.start(this)
// Optional: register providers for deeper inspection
AppReveal.registerStateProvider(myStateContainer)
AppReveal.registerNavigationProvider(myRouter)
AppReveal.registerFeatureFlagProvider(myFeatureFlags)
AppReveal.registerNetworkObservable(myNetworkClient)
}
}
}WebView support works automatically -- no additional integration needed.
Screen identity is auto-derived from class names -- LoginFragment becomes key "login", OrderDetailActivity becomes "order.detail". Override only when you want a custom key:
class LoginFragment : Fragment(), ScreenIdentifiable {
override val screenKey = "auth.login"
override val screenTitle = "Login"
}Use android:tag in layouts for element identification (the Android equivalent of iOS accessibilityIdentifier):
<EditText
android:id="@+id/emailField"
android:tag="login.email" />
<EditText
android:id="@+id/passwordField"
android:tag="login.password" />
<Button
android:id="@+id/loginButton"
android:tag="login.submit" />Or set programmatically:
emailField.tag = "login.email"Element IDs are resolved in this order:
- Custom AppReveal tag:
view.getTag(R.id.appreveal_id) - Android resource ID name:
resources.getResourceEntryName(view.id)(e.g.,"emailField") - View tag:
view.tagas String - Content description:
view.contentDescription
# The server port is logged to logcat:
# [AppReveal] MCP server listening on port 56209
# Initialize MCP session
curl -X POST http://<device-ip>:<port>/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
# Get current screen
curl -X POST http://<device-ip>:<port>/ \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_screen","arguments":{}}}'For emulator, forward the port first:
adb forward tcp:56209 tcp:56209
curl -X POST http://localhost:56209/ .../// Expose app state
interface StateProviding {
fun snapshot(): Map<String, Any?>
}
/// Expose navigation state
interface NavigationProviding {
val currentRoute: String
val navigationStack: List<String>
val presentedModals: List<String>
}
/// Expose feature flags
interface FeatureFlagProviding {
fun allFlags(): Map<String, Any?>
}
/// Expose network traffic
interface NetworkObservable {
val recentRequests: List<CapturedRequest>
fun addObserver(observer: NetworkTrafficObserver)
}- Library added as
debugImplementation-- not included in release APK - Local network only
- Sensitive headers (Authorization, Cookie) redacted in network capture
- Transport: NanoHTTPD (embedded HTTP server)
- Discovery: NsdManager (Android Network Service Discovery)
- View hierarchy: ViewGroup tree walking
- Screenshots: PixelCopy (API 26+) / View.drawToBitmap
- WebView: android.webkit.WebView + evaluateJavascript
- Thread model: NanoHTTPD worker threads + MainThreadExecutor for UI access
See example/Android/ for a full example with 11 screens matching the iOS example, with all framework features integrated.