SDK Configuration
Auto capture needs three integration points:Userpilot.initialize(..., UserpilotOptions(...))— starts the native Userpilot SDK and, when autocapture options are provided, starts the Dart autocapture pipeline.UserpilotAutocaptureWidget(child: ...)— wraps your app so the SDK can attach a global pointer route and listen for scroll settle notifications (PageView,TabBarView, wheel pickers).UserpilotAutocaptureNavigatorObserver()— added to eachNavigatoryou want tracked for$screenand dialog/bottom-sheetview_presentedevents.
Userpilot.initialize(...) when your app boots (often from an initState / splash path after WidgetsFlutterBinding.ensureInitialized() if you await it before runApp).
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).
GoRouter
Add the observer to GoRouter’sobservers list. For nested navigators (ShellRoute, StatefulShellBranch), add a fresh observer instance per navigator:
StatefulShellRoute.indexedStackkeeps every branch alive in anIndexedStack. Switching tabs does not push or pop, so$screenonly fires on the first visit to each branch. Use a non-statefulShellRouteif you need a screen event on every tab swap.CustomTransitionPagemust be constructed withkey: state.pageKeyandname: state.matchedLocation. Withoutname, the generated page route is unnamed and no$screenevent is emitted.
AutoRoute
Pass a fresh observer instance through AutoRoute’snavigatorObservers 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:
Interaction Capture
WhenenableInteractionAutoCapture: true, the SDK emits an event for the following user interactions:
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, passenableInteractionValueCapture: 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 inpayload_schema.dart (_allowedKeys). Common fields:
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 whenenableInteractionAccessibilityLabelCapture: false, underUserpilotRedactText, or when no label exists.attr__id—Semantics.identifierorValueKeyvalue when present.Semantics.identifieris omitted whenenableInteractionAccessibilityLabelCapture: falseor underUserpilotRedactText;ValueKeyremains structural metadata.attr__index— sibling index under the parent.
;. 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.
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.
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:Userpilot.initialize(...).
While stopped, no screen or interaction events are recorded regardless of other configuration. This is a global toggle that overrides per-subtree settings.
Limitations
- Routes pushed without
RouteSettings.nameare ignored for screen autocapture. Always pass aname(or use named routes viaonGenerateRoute) when a route should be tracked as a screen. - Multiple
Navigatorinstances — every nestedNavigatorhas its ownobserverslist. AddUserpilotAutocaptureNavigatorObserver()to each navigator you want tracked. - Non-
PageRoutetransitions — the observer only treatsPageRoutesubclasses as screens. Custom transitions that extendTransitionRoutedirectly are not captured. Bothgo_routerandauto_routeproducePageRoutesubclasses 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.