- 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") // release-safe empty artifact
}The appreveal-noop module provides empty release methods 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.
When the listener is ready, AppReveal logs a loopback URL, an authenticated session URL, and the session token. You can also read them from AppReveal.url, AppReveal.sessionUrl, and AppReveal.sessionToken.
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
Compose requires no additional AppReveal dependency or accessibility-service setup. AppReveal reads
the merged semantics tree exposed by AndroidComposeView, so get_elements, get_view_tree,
tap_element, tap_text, type_text, and clear_text work with Compose controls on API 26+.
Use Modifier.testTag for stable IDs:
OutlinedTextField(
value = email,
onValueChange = { email = it },
modifier = Modifier.testTag("login.email"),
)
Button(
onClick = ::submit,
modifier = Modifier.testTag("login.submit"),
) {
Text("Sign in")
}AppReveal reads TestTag directly; testTagsAsResourceId is not required. When a tag is absent,
content descriptions and visible or editable text provide derived IDs. Compose actions are invoked
through their semantics callbacks, so the feature does not depend on TalkBack being enabled.
# The server port is logged to logcat:
# [AppReveal] MCP server listening on port 56209
# [AppReveal] Session URL: http://127.0.0.1:56209/?appreveal_session_token=<token>
# Check listener health. This endpoint is intentionally unauthenticated.
curl http://<device-ip>:<port>/health
TOKEN="<session-token>"
# Initialize MCP session
curl -X POST http://<device-ip>:<port>/ \
-H "Authorization: Bearer $TOKEN" \
-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 "Authorization: Bearer $TOKEN" \
-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)
}Android apps that use OkHttp can capture real HTTP traffic, including response bodies and
text/event-stream / Server-Sent Event frames, by installing the debug interceptor on the
app's OkHttp client:
val client = AppRevealOkHttp.install(OkHttpClient.Builder()).build()For explicit limits or extra header redaction:
val client = AppRevealOkHttp.install(
OkHttpClient.Builder(),
NetworkCaptureConfig(
maxBodyBytes = 64L * 1024L,
redactedHeaders = CapturedRequest.defaultSensitiveHeaders + "x-session-token",
),
).build()get_network_calls lists captured calls; get_network_call_detail returns one call's request
and response headers, captured text bodies, truncation flags, and parsed SSE frames. Release
builds use the appreveal-noop helper, so shared debug/release networking setup can keep the
same calls while the release interceptor simply passes through.
Parity note: Android has OkHttp body/SSE detail capture. iOS exposes the same
get_network_call_detail tool and captures URLSession text body previews; app-fed integrations
can also provide body fields. macOS/Flutter/React Native keep their existing app-fed or fetch
summary capture until matching automatic body-detail hooks are implemented there.
- Library added as
debugImplementation-- not included in release APK - Generated per-session token required for MCP POST requests
- Health diagnostics available at
GET /health - Loopback CORS only
- NsdManager advertises
_appreveal._tcpwithauth=session-token - Sensitive headers (Authorization, Cookie, Set-Cookie, x-api-key, x-auth-token) redacted in network capture
- Transport: NanoHTTPD (embedded HTTP server)
- Discovery: NsdManager (Android Network Service Discovery)
- View hierarchy: ViewGroup walking plus dependency-free Jetpack Compose semantics traversal
- 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 View-based screens plus a
Compose semantics fixture, with all framework features integrated.