Skip to main content
Configure automatic screen and interaction capture for Android View and Compose applications using the Userpilot Gradle Plugin and Userpilot SDK. The Userpilot Android SDK can automatically capture Android View and Compose screens and interactions, enabling analytics and engagement without manual screen and event tracking. Use this guide for SDK-side configuration; build-time instrumentation is configured in the app module with the Userpilot Gradle Plugin.

SDK Configuration

Auto capture behavior is configured through UserpilotConfig during SDK initialization.
val userpilot = Userpilot(context, "<APP_TOKEN>") {
    enableScreenAutoCapture = true
    enableInteractionAutoCapture = true
    enableInteractionTextCapture = true
    enableInteractionAccessibilityLabelCapture = true
    enableInteractionValueCapture = false
    uiFramework = UiFramework.Compose // or UiFramework.View
}
OptionTypeDefaultDescription
enableScreenAutoCaptureBooleanfalseEnables automatic screen tracking. View apps use Activity/Fragment lifecycle tracking. Compose apps should use UserpilotComposeNavigationTracker(...) for navigation-aware screen capture.
enableInteractionAutoCaptureBooleanfalseEnables automatic interaction capture. Requires Gradle plugin instrumentation for the UI framework being captured.
enableInteractionTextCaptureBooleantrueIf false, user-visible text is redacted from captured interaction payloads.
enableInteractionAccessibilityLabelCaptureBooleantrueIf false, accessibility labels/content descriptions are not captured.
enableInteractionValueCaptureBooleanfalseEnables value payload capture: is_checked, selected date/time, slider/seekbar values, and on spinner / ListPopupWindow / Compose collections both selected_index and selected_value. AdapterView list item clicks always include selected_index; selected_value only when this flag is true.
uiFrameworkUiFramework?nullOptional framework hint: UiFramework.View or UiFramework.Compose.
NoteAuto Capture automatically tracks screen views and ignores manually sent screen events while enabled.If you are migrating from manual screen tracking, please review the migration guide here.

Gradle Plugin Configuration

Auto capture also needs build-time instrumentation in the app module. The plugin is distributed through Maven Central. Add the Userpilot Gradle plugin to your app module build.gradle and replace <latest_version> with the latest release version.
plugins {
    id("com.userpilot.plugin") version "<latest_version>"
}

userpilot {
    enabled.set(true)
    viewEnabled.set(true)
    composeEnabled.set(true)
    composeHierarchy {
        row.set(true)
        column.set(false)
        box.set(false)
        card.set(true)
        surface.set(true)
    }
    logging.set(false)
}
OptionDefaultDescription
enabledtrueMaster switch for plugin instrumentation.
viewEnabledfalseInstruments Android View call sites.
composeEnabledfalseInstruments Compose call sites.
composeHierarchy.rowtrueAdds inline Row nodes to Compose hierarchy paths.
composeHierarchy.columnfalseAdds inline Column nodes to Compose hierarchy paths.
composeHierarchy.boxfalseAdds inline Box nodes to Compose hierarchy paths.
composeHierarchy.cardtrueAdds Material 3 card-family nodes to Compose hierarchy paths.
composeHierarchy.surfacetrueAdds Material 3 Surface nodes to Compose hierarchy paths.
loggingfalseEnables plugin diagnostic logging during builds.
Enable viewEnabled, composeEnabled, or both depending on the UI frameworks used by the app.

Android View Capture

View screen capture is handled by Activity and Fragment lifecycle callbacks. Internal Userpilot surfaces are excluded.

View Screen Tracking

Activities are tracked from lifecycle resume events using the manifest label when available, otherwise the Activity class name. Fragments are tracked by class name, except dialog/bottom-sheet fragments and fragments marked with userpilotIgnoreScreen. Use userpilotIgnoreScreen when a View Activity or Fragment is auxiliary content that should not emit its own automatic screen-view event. Typical cases: a host activity that delegates the visible screen to a child, or a fragment composed as one slot inside a split-pane / tabbed / nested-fragment layout where the parent already identifies the screen.
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.userpilot.autoCapture.view.apis.userpilotIgnoreScreen

class DetailsHostActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        userpilotIgnoreScreen = true
    }
}

class DetailsPaneFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        userpilotIgnoreScreen = true
    }
}
Captured View interactions include:
  • Clicks and long clicks
  • Clickable spans
  • Dialog and bottom-sheet presentation
  • Dialog button clicks
  • Adapter item clicks and selections
  • Text changes
  • CompoundButton, SeekBar, Slider, RangeSlider, date picker, and time picker changes

Improving Android Autocapture

InteractionSupported TypesRequired Listener
View ClickAll subclasses of View.View.OnClickListener must be set to the target view.
In-Text Link ClickTextView and its subclasses.In-text link must be generated using a ClickableSpan that overrides the onClick method.
Dialog Button ClickAll dialog types that implement DialogInterface (e.g. AlertDialog).DialogInterface.OnClickListener must be set to the target dialog button.
Dialog List Item ClickPlatform native and AndroidX implementations of AlertDialog.Must attach a DialogInterface.OnClickListener to the target dialog’s list using AlertDialog.Builder.setItems().
List Item ClickAll subclasses of AdapterView.AdapterView.OnItemClickListener must be set to the target list.
Spinner Item SelectedAll subclasses of AdapterView, but works best with Spinner and its subclasses.AdapterView.OnItemSelectedListener must be set to the target view.
Toggle Control Value ChangedAll subclasses of CompoundButton (e.g. ToggleButton, CheckBox, Switch) except RadioButton.CompoundButton.OnCheckedChangeListener must be set to the target view.
Radio Group Value ChangedRadioGroup and its subclasses.RadioGroup.OnCheckedChangeListener must be set to the target view.
Text Input Value ChangedEditText and its subclasses.TextWatcher must be set to the input field using TextView.addTextChangedListener().
Date Value ChangedDatePickerDialogDatePickerDialog.OnDateSetListener must be set to the target dialog.
Time Value ChangedTimePickerDialogTimePickerDialog.OnTimeSetListener must be set to the target dialog.
SeekBar Value ChangedSeekBarSeekBar.OnSeekBarChangeListener must be set to the target view.
Slider Value ChangedMaterial component library implementations of Slider and RangeSlider.No required listeners needed for value change capture. The SDK attaches its own listener.

View Privacy Helpers

Use these helpers for View-specific privacy and payload control:
  • Userpilot.redactText(view)
  • Userpilot.ignoreInteractions(view)
  • Userpilot.overrideTargetText(view, title)
  • Userpilot.clearTargetTextOverride(view)
  • Userpilot.enableCompoundButtonValueChangeCapture(compoundButton)
  • Userpilot.disableCompoundButtonValueChangeCapture(compoundButton)
  • Userpilot.setShouldCaptureValueOnInteractionChangeEvents(shouldCaptureValue)
XML tags are also supported:
android:tag="userpilotIgnoreInteractions"
android:tag="userpilotRedactText"

Compose Capture

The following Compose interactions are supported:
  • Clickable and combined-clickable taps
  • Toggleable, checkbox, switch, radio button, and icon toggle changes
  • Selectable items, tabs, navigation items, chips, and dropdown menu items
  • Text field changes, debounced per field holder
  • Slider, date picker, and time picker changes
  • Dialog, alert dialog, modal bottom sheet, and exposed dropdown presentation

Limitations

  • Material2 Compose components are intentionally not instrumented. Use Material3 — the wrappers are tuned for Material3 1.3.+ and are expected to work with Material3 1.1.0+. Apps pinned to Material3 below 1.1.0 may see missing or misbehaving Compose instrumentation. Plain foundation controls (BasicTextField via the String and TextFieldValue overloads) are covered regardless of Material version.
  • Compose screen tracking is opt-in. Activities and Fragments are tracked automatically when enableScreenAutoCapture = true via the same Activity / Fragment lifecycle pipeline that View auto capture uses. Compose-driven screens require one of the explicit helpers: UserpilotComposeNavigationTracker(navController), UserpilotComposeNavigationTracker(backStack = ...), or Modifier.userpilotScreen("title") (see Compose Screen Tracking). There is no automatic route-name detection — pick one of these wrappers per nav graph or per screen.
  • Custom-built Compose components are not auto-instrumented. The Gradle plugin rewrites known foundation / Material3 call sites (Modifier.clickable, Modifier.toggleable, Button, Checkbox, ModalBottomSheet, etc.). A composable that implements its own interaction handling without going through those primitives won’t be captured. Use the supported building blocks to inherit auto capture for free.

Compose Screen Tracking

@Composable
fun App() {
    val navController = rememberNavController()

    UserpilotComposeNavigationTracker(
        navController = navController,
        screenNameResolver = { route -> route.substringAfterLast(".") },
        screenFilter = { route -> !route.startsWith("debug") },
    )
}
For a single Compose screen outside of a nav graph (for example, a one-off screen launched from XML, a settings overlay, or any Composable hosted directly in an Activity), attach Modifier.userpilotScreen(...) to the screen’s root container. It fires a screen(...) event every time the host lifecycle reaches RESUMED, so the report is aligned with the user actually seeing the screen rather than with composition entry:
@Composable
fun ProfileScreen() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .userpilotScreen(screenTitle = "Profile"),
    ) {
        // screen content
    }
}

Compose Privacy Helpers

Use subtree-scoped helpers for broad privacy rules. The default is to opt out / redact everything in the wrapped subtree:
UserpilotIgnoreInteractions {
    SensitiveSettings()
}

UserpilotRedactText {
    ProfileForm()
}
Both helpers also accept an explicit boolean so you can carve out an exception inside a larger opt-out — for example, re-enable a specific section while everything around it is ignored:
UserpilotIgnoreInteractions {
    SensitiveSettings()

    UserpilotIgnoreInteractions(ignore = false) {
        // captured normally, even though the outer scope opts out
        FeedbackPrompt()
    }
}
Use modifier-level helpers for one element. Unlike the subtree composables, the effect does not propagate into the element’s content slot, and an explicit modifier value wins over any inherited scope:
Button(
    modifier = Modifier.userpilotIgnoreInteractions(),
    onClick = onClick,
) {
    Text("Internal action")
}

TextField(
    modifier = Modifier.userpilotRedactText(),
    value = value,
    onValueChange = onValueChange,
)
Both modifier helpers accept the same ignore: Boolean = true / redact: Boolean = true parameter, so you can use them to undo an inherited scope at the element level:
UserpilotRedactText {
    ProfileForm()
    // The job title is product copy, not PII — keep it captured.
    Text(
        text = "Senior Engineer",
        modifier = Modifier.userpilotRedactText(redact = false),
    )
}

Runtime Controls

These APIs stop or resume auto capture:
Userpilot.stopAutoCapture()

// ... sensitive flow ...

Userpilot.resumeAutoCapture()
While stopped, no screen or interaction events are recorded regardless of other configuration. This is a global toggle that overrides per-view settings.