> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userpilot.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Auto Capture

> Configure auto capture for the Userpilot Flutter SDK

Configure automatic screen and interaction capture for Flutter applications using the Userpilot Flutter SDK.

The Userpilot Flutter SDK can automatically capture screen views and user interactions without any explicit instrumentation in your app. Auto Capture runs entirely in Dart — no build-time plugin is required, and the same Dart integration is used on Android and iOS.

***

## SDK Configuration

Auto capture needs three integration points:

1. **`Userpilot.initialize(..., UserpilotOptions(...))`** — starts the native Userpilot SDK and, when autocapture options are provided, starts the Dart autocapture pipeline.
2. **`UserpilotAutocaptureWidget(child: ...)`** — wraps your app so the SDK can attach a global pointer route and listen for scroll settle notifications (`PageView`, `TabBarView`, wheel pickers).
3. **`UserpilotAutocaptureNavigatorObserver()`** — added to each `Navigator` you want tracked for `$screen` and dialog/bottom-sheet `view_presented` events.

Call `Userpilot.initialize(...)` when your app boots (often from an `initState` / splash path after `WidgetsFlutterBinding.ensureInitialized()` if you await it before `runApp`).

```dart theme={null}
import 'package:flutter/material.dart';
import 'package:userpilot_flutter/userpilot_flutter.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Screen and interaction capture are opt-in (default false).
  final options = UserpilotOptions(
    logging: true,
    enableScreenAutoCapture: true,
    enableInteractionAutoCapture: true,
    enableInteractionAccessibilityLabelCapture: true,
    enableInteractionTextCapture: true,
    enableInteractionValueCapture: false,
  );
  Userpilot.initialize('<APP_TOKEN>', options);

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return UserpilotAutocaptureWidget(
      child: MaterialApp(
        navigatorObservers: [UserpilotAutocaptureNavigatorObserver()],
        home: const HomePage(),
      ),
    );
  }
}
```

| **Option**                                   | **Type** | **Default** | **Description**                                                                                                                                                                                                                                      |
| -------------------------------------------- | -------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enableScreenAutoCapture`                    | `bool`   | `false`     | Enables automatic screen tracking; captures `$screen` on push/pop/replace.                                                                                                                                                                           |
| `enableInteractionAutoCapture`               | `bool`   | `false`     | Enables automatic interaction tracking: tap, value-change, selection-change, text-change, and `view_presented`.                                                                                                                                      |
| `enableInteractionAccessibilityLabelCapture` | `bool`   | `true`      | Captures accessibility/semantic metadata (label, identifier, hint, value, tag).                                                                                                                                                                      |
| `enableInteractionTextCapture`               | `bool`   | `true`      | Captures visible text labels (button titles, list-tile titles, text-field labels/placeholders, dialog titles, trigger-source titles, etc.). Set `false` to strip those text fields from interaction payloads. Truncated to a fixed cap when enabled. |
| `enableInteractionValueCapture`              | `bool`   | `false`     | Captures user-input values (slider value, toggle state, picked date/time, dropdown choice). Off by default for privacy.                                                                                                                              |
| `maxHierarchyDepth`                          | `int`    | `25`        | Maximum number of ancestor widgets included in event metadata.                                                                                                                                                                                       |

<Tip>
  **Note**

  Auto 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](https://docs.userpilot.com/data-events/mobile-screen-tracking/mobile-screen-auto-capture#important).
</Tip>

***

## Screen Capture

`UserpilotAutocaptureNavigatorObserver()` returns a plain `NavigatorObserver`. Whenever Flutter pushes, pops, or replaces a named `PageRoute`, the observer emits a `$screen` event whose `title` is the route's `RouteSettings.name`. Routes without an explicit name are ignored so framework-generated route classes do not leak into screen analytics.

The observer is stateless and safe to instantiate multiple times. Add it to **every** `Navigator` you want tracked (root navigator, shell navigators, tab branch navigators).

<Tip>
  **Manual screens**

  `Userpilot.screen('<title>')` is a no-op while screen autocapture is enabled. To send screens manually, leave `enableScreenAutoCapture: false` in `UserpilotOptions`.
</Tip>

### GoRouter

Add the observer to GoRouter's `observers` list. For nested navigators (`ShellRoute`, `StatefulShellBranch`), add a fresh observer instance per navigator:

```dart theme={null}
final _router = GoRouter(
  observers: [UserpilotAutocaptureNavigatorObserver()],
  routes: [
    GoRoute(path: '/', builder: (_, __) => const HomePage()),
    ShellRoute(
      observers: [UserpilotAutocaptureNavigatorObserver()],
      builder: (context, state, child) => MyShellScaffold(child: child),
      routes: [/* ... */],
    ),
  ],
);
```

GoRouter limitations:

* **`StatefulShellRoute.indexedStack`** keeps every branch alive in an `IndexedStack`. Switching tabs does not push or pop, so `$screen` only fires on the **first visit** to each branch. Use a non-stateful `ShellRoute` if you need a screen event on every tab swap.
* **`CustomTransitionPage`** must be constructed with `key: state.pageKey` and `name: state.matchedLocation`. Without `name`, the generated page route is unnamed and no `$screen` event is emitted.

### AutoRoute

Pass a fresh observer instance through AutoRoute's `navigatorObservers` builder. The builder is invoked once per nested router (root, every `AutoTabsRouter` branch, etc.), so every navigator in the tree gets its own observer with no extra setup:

```dart theme={null}
return MaterialApp.router(
  routerConfig: _appRouter.config(
    navigatorObservers: () => [UserpilotAutocaptureNavigatorObserver()],
  ),
);
```

***

## Interaction Capture

When `enableInteractionAutoCapture: true`, the SDK emits an event for the following user interactions:

| **Interaction type**     | **Emitted for**                                                                                                                                                                                                                                               |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `touch`                  | Generic taps on buttons and gesture surfaces such as `ElevatedButton`, `FilledButton`, `TextButton`, `OutlinedButton`, `IconButton`, `FloatingActionButton`, `BackButton`, `CloseButton`, `CupertinoButton`, `InkWell`, `InkResponse`, and `GestureDetector`. |
| `switch_changed`         | `Switch`, `SwitchListTile`, `CupertinoSwitch`                                                                                                                                                                                                                 |
| `checkbox_selected`      | `Checkbox`, `CheckboxListTile`, `CupertinoCheckbox`                                                                                                                                                                                                           |
| `toggle_button_selected` | `ToggleButtons`, `SegmentedButton`                                                                                                                                                                                                                            |
| `radio_button_selected`  | `Radio`, `RadioListTile`, `CupertinoRadio`                                                                                                                                                                                                                    |
| `slider_changed`         | `Slider`, `RangeSlider`, `CupertinoSlider`                                                                                                                                                                                                                    |
| `chip_selected`          | `ChoiceChip`, `FilterChip`, `InputChip`, `ActionChip`                                                                                                                                                                                                         |
| `date_picker_changed`    | `DatePicker`, `CalendarDatePicker`, `CupertinoDatePicker`                                                                                                                                                                                                     |
| `time_picker_changed`    | `TimePicker`, `CupertinoTimerPicker`                                                                                                                                                                                                                          |
| `text_field_changed`     | `TextField`, `TextFormField`, `CupertinoTextField` (debounced; typed text is not captured)                                                                                                                                                                    |
| `list_item_selected`     | `ListView` / `ListTile` rows                                                                                                                                                                                                                                  |
| `spinner_selected`       | `DropdownButton`, `DropdownMenu` items                                                                                                                                                                                                                        |
| `wheel_item_selected`    | `CupertinoPicker`, wheel scroll views                                                                                                                                                                                                                         |
| `page_selected`          | `PageView` swipes                                                                                                                                                                                                                                             |
| `tab_host_selected`      | `TabBar` tabs (the in-screen tab strip)                                                                                                                                                                                                                       |
| `tab_selected`           | `BottomNavigationBar`, `NavigationBar`, `NavigationRail` items                                                                                                                                                                                                |
| `menu_item_selected`     | `PopupMenuButton` items, `MenuAnchor` items                                                                                                                                                                                                                   |
| `view_presented`         | Dialog and bottom-sheet presentation                                                                                                                                                                                                                          |

Each interaction maps to a high-level analytics category (`tap`, `text_change`, `selection_change`, `value_change`, `view_presented`) — that is the event name that reaches your analytics backend.

### Component Value Gating

By default, user-input values are stripped from event payloads — slider values, toggle states, picked dates and times, and dropdown selections do not leave the device. To include them, pass `enableInteractionValueCapture: true` in `UserpilotOptions`. Position and configuration fields (`selected_index`, `min`, `max`, `direction`) are always captured because they don't reveal user input.

***

## Privacy Controls

### Wire Metadata Keys

Interaction events use the keys in `payload_schema.dart` (`_allowedKeys`). Common fields:

| **Key**                                                                                                    | **Meaning**                                                                                            |
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `target_class`                                                                                             | Classified widget type (e.g. `ElevatedButton`, `AlertDialog`)                                          |
| `target_text`                                                                                              | Primary visible label / title for the control                                                          |
| `deepest_target_type`                                                                                      | Widget type at the hit point                                                                           |
| `hierarchy`                                                                                                | Semicolon-separated ancestor path (see below)                                                          |
| `accessibility_label`, `accessibility_identifier`, `accessibility_hint`, `accessibility_value`, `view_tag` | Flattened semantics metadata                                                                           |
| `selected_index`                                                                                           | List/tab/page index                                                                                    |
| `selected_value`, `values`                                                                                 | Captured value for single-value and multi-value controls when value capture is enabled                 |
| `placeholder`                                                                                              | Text-field hint / decoration hint                                                                      |
| `presentation_surface`                                                                                     | Presented UI component type (e.g. `AlertDialog`, `BottomSheet`) — not `system_dialog` / `bottom_sheet` |

Screen events (`$screen`) still use a single `title` field on the native bridge.

### `hierarchy` Format

Built from the hit element up to `maxHierarchyDepth`, then the current screen name. Each segment looks like:

`WidgetType:attr__desc="…",attr__id="…",attr__index="n"`

* `attr__desc` — only the semantics **accessibility label** for that ancestor. Omitted when `enableInteractionAccessibilityLabelCapture: false`, under `UserpilotRedactText`, or when no label exists.
* `attr__id` — `Semantics.identifier` or `ValueKey` value when present. `Semantics.identifier` is omitted when `enableInteractionAccessibilityLabelCapture: false` or under `UserpilotRedactText`; `ValueKey` remains structural metadata.
* `attr__index` — sibling index under the parent.

Segments are joined with `;`. Under `UserpilotRedactText`, hierarchy is preserved but text/accessibility attributes are omitted so redacted text does not appear in the path.

### `UserpilotRedactText`

Wraps a subtree so autocapture replaces every text label it would otherwise capture (`target_text`, `placeholder`, dialog titles, trigger source names, accessibility strings, etc.) with `****`. The interaction event still fires — the widget is still identified by type, position, and structure — only the human-readable text is masked.

Capture configuration is applied before redacted values leave the SDK. If `enableInteractionTextCapture: false`, text fields such as `target_text` are removed from the payload instead of being sent as `****`. If `enableInteractionAccessibilityLabelCapture: false`, accessibility fields are removed from the payload and from hierarchy attributes.

```dart theme={null}
UserpilotRedactText(
  child: ElevatedButton(
    onPressed: _confirm,
    child: const Text('Account 4242 4242 4242 4242'),
  ),
);
```

`hierarchy` remains on redacted events, but text/accessibility attributes such as `attr__desc` and `Semantics.identifier` are omitted. Remove the wrapper to disable masking for that subtree.

### `UserpilotIgnoreInteractions`

Wraps a subtree so autocapture skips **interaction** events whose hit-tested widget lies inside it (taps, value changes, scroll selections, debounced text-field updates tied to that field, etc.). Route-driven **`$screen`** events are **not** suppressed by `UserpilotIgnoreInteractions` — they come from the navigator observer, not from widget hit testing.

The host app's own gesture handlers (`onPressed`, `onTap`, …) still run — only autocapture ignores those interactions.

```dart theme={null}
UserpilotIgnoreInteractions(
  child: ElevatedButton(
    onPressed: _internalAction,
    child: const Text('Internal-only action'),
  ),
);
```

Use `UserpilotIgnoreInteractions` for interactions whose mere occurrence would leak intent (e.g. tapping a row in a contacts list, where row position correlates 1:1 with contact identity).

Both widgets are passive markers — their `build` returns `child` unchanged. Wrap the **outermost** widget you want affected; descendants of the wrapper are covered, ancestors are not.

***

## Runtime Controls

To stop or resume collection without changing options — for example a "private mode" — use:

```dart theme={null}
Userpilot.stopAutocapture();   // stops capture; preserves options
Userpilot.resumeAutocapture(); // resumes capture
```

Both calls are idempotent. They preserve the options supplied to `Userpilot.initialize(...)`.

***

## Limitations

* **Routes pushed without `RouteSettings.name`** are ignored for screen autocapture. Always pass a `name` (or use named routes via `onGenerateRoute`) when a route should be tracked as a screen.
* **Multiple `Navigator` instances** — every nested `Navigator` has its own `observers` list. Add `UserpilotAutocaptureNavigatorObserver()` to **each** navigator you want tracked.
* **Non-`PageRoute` transitions** — the observer only treats `PageRoute` subclasses as screens. Custom transitions that extend `TransitionRoute` directly are not captured. Both `go_router` and `auto_route` produce `PageRoute` subclasses for their default page builders, so this only affects bespoke routing implementations.
* **Text-field attachment** — editable fields are discovered when autocapture is registered and when focus moves (and during internal rescans). A field that appears without a focus change may not be watched until the next scan trigger.
* **Duplicate taps** — identical tap payloads within a short window (\~500 ms) may be dropped to avoid double events from rebuilds or pointer routing.

<Frame>
  [**For any questions or concerns please reach out to support@userpilot.com**](mailto:support@userpilot.com)
</Frame>
