Android
The Android SDK adds debug-only capture to classic Views and Jetpack Compose apps, on an emulator or a USB-connected device. A no-op artifact keeps the same API for Release builds.
Prefer to start from working code? The android sample is set up with the published SDK.
Gradle setup
The SDK is on Maven Central. Use the capture artifact in Debug builds and the no-op artifact in Release builds:
// app/build.gradle.kts
dependencies {
debugImplementation("dev.pointfix:pointfix-android:0.2.0")
releaseImplementation("dev.pointfix:pointfix-android-noop:0.2.0")
}
The no-op artifact has the same API, so Release builds compile unchanged with capture removed. Make sure mavenCentral() is in your repositories. Apps target API 26 or later. The SDK depends on Compose UI, even for View-based apps.
Install capture
Install once per Activity, after setContent or setContentView, and close the handle when the Activity is destroyed. Call these APIs on the main thread.
import dev.pointfix.CaptureHandle
import dev.pointfix.Pointfix
private var capture: CaptureHandle? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { CheckoutScreen() } // Or setContentView(...)
capture = Pointfix.install(this, screen = "Checkout")
}
override fun onDestroy() {
capture?.close()
capture = null
super.onDestroy()
}
| API | Purpose |
|---|---|
Pointfix.install(activity, screen, port) | Capture for the bridge on the connected Mac, through adb reverse. screen defaults to the Activity’s class name, port to 4747 (1024–65535). |
Pointfix.install(activity, screen, url = "http://…:4747") | Capture for a bridge on another Mac, with remote access on. The URL must be http or https with a host. |
Pointfix.mark(view, name, file, line) | Mark a View with its source location. |
Modifier.pointfixable(name, file, line) | Mark a Compose element with its source location. |
CaptureHandle.close() | Detach capture and stop polling. |
SourceMark(name, file, line) | Source descriptor; file and line are optional. |
Installing again on the same Activity detaches the earlier host. Closing restores the original window callback, cancels gestures and polling, dismisses the composer and shuts down the background executor. The agent and model are chosen in Pointfix’s Preferences, not in the app.
Connect with adb reverse
The SDK connects to 127.0.0.1 on the device, so forward the bridge’s port from the device to your Mac:
adb reverse tcp:4747 tcp:4747
Match the port if you changed it, add -s SERIAL when several devices are attached, and run it again after reconnecting a device: reconnecting can remove the forwarding.
For a bridge on another Mac, use its URL from Connections instead, preferably its IP address (emulators often can’t resolve .local names):
capture = Pointfix.install(this, screen = "Checkout", url = "http://192.168.1.20:4747")
Allow local HTTP in Debug
The Debug artifact declares the INTERNET permission. Allow cleartext HTTP in your application’s src/debug/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:usesCleartextTraffic="true" />
</manifest>
An existing network security configuration can override this flag. If you have one, allow localhost in your development configuration.
Element context
No per-control Pointfix code is needed. Views contribute their resource ID and content description; Compose contributes existing test tags, text and content descriptions through Compose’s public testing semantics interface (tested with Compose 1.7.6 — check capture when you upgrade Compose). No accessibility service or test-tag resource-ID setting is needed.
import androidx.compose.ui.platform.testTag
Button(
onClick = ::continueCheckout,
modifier = Modifier.testTag("checkout.continue")
) { Text("Continue") }
For an exact file and line, add a source marker. Android does not infer them from stack traces, so pass repository-relative paths yourself:
import dev.pointfix.pointfixable
Button(
onClick = ::submit,
modifier = Modifier.pointfixable("checkout.submit", "app/CheckoutScreen.kt", 42)
) { Text("Submit") }
// Classic View:
Pointfix.mark(submitButton, "checkout.submit", "app/CheckoutActivity.kt", 42)
Nested Compose markers use the smallest containing frame and are removed when their composition is disposed. Nested View markers prefer the deepest marked View. Without IDs or markers, a report still has the screenshot and the touch point (in window pixels).
Capture and the composer
Hold an element for 600 ms. Short taps, and touches that move beyond the touch slop, pass through to your app. When the hold triggers, the original touch is cancelled, the window is screenshotted with the element outlined, and a native composer opens:
- The element’s marker name, identifier or label, with the screen, “Android” and the source file.
- Optional category chips: Spacing, Color, Text, Size, Layout, Other.
- What should change? and Send.
- { } Context (expert mode): a read-only preview of the Context Packet, refreshed 400 ms after you stop typing. The choice is remembered.
Progress pill
A progress pill shows the status, elapsed time and the Sent, Agent, Relaunch and Review steps. In review it offers Commit fix and I’ll commit, which work like Review & commit. Polling stops when the report is finished or failed, or after three connection errors (Bridge unreachable).
The SDK saves the latest report ID and records a launch when the host is installed again. When the report is in review and the reported screen is showing, it sends that screen as the Proposed screenshot.
Release builds
pointfix-android-noop keeps the API but contains no capture host, screenshot code or HTTP client. Every method is a no-op.
Limitations
- Separate Dialog windows are not intercepted.
- The screenshot can omit SurfaceView, video and OpenGL content.
- Screens with
FLAG_SECUREcan’t be captured. - Unmarked Compose content has no full semantics-tree extraction beyond test tags, text and content descriptions.
- If your app replaces the window callback itself, install and close Pointfix carefully around it.
- The long-press can compete with long-press gestures in your app.
- Capture on a real emulator or device has not been verified on Pointfix’s build machine; the SDK is covered by Robolectric tests.
Pointfix