Track screens
Record every screen your users visit, automatically for activities and with one line for Compose Navigation.
The SDK records a built-in $screen event each time the user lands on a new screen. Each event
carries the screen the user came from and how long they stayed there, so one event describes a
whole transition. Screen tracking is on by default.
Activities
Nothing to add. When an activity resumes, the SDK records it with the class name as the screen name once the resume has completed. An activity that finishes before then is not recorded. Returning from the background does not record the same activity again.
Compose Navigation
Single-activity apps navigate inside one activity, so attach the tracker to your NavController:
val navController = rememberNavController()
DisposableEffect(navController) {
val tracking = VoidhashScreenTracking.attach(navController)
onDispose { tracking.close() }
}The screen name is the destination route pattern, such as item/{id}, and the path is the route
with arguments filled in, such as item/42. While a controller is attached, activity resumes are
not reported, including the resume of the activity that attaches it. A rotation reports neither
the recreated activity nor the current destination again. Closing the handle detaches the listener
and lets activity screens through again.
Fragments
Turn on fragment tracking to record fragments as screens:
Voidhash.configure(
context,
"vh_pk_...",
VoidhashOptions(screenTracking = ScreenTrackingOptions(fragments = true)),
)Each resumed fragment is recorded as a screen. An activity that shows a tracked fragment is not reported itself; other activities still are. These fragments are skipped:
- Fragments without a view, which libraries use for background work.
- Hidden fragments.
- Dialogs, including bottom sheets. The screen underneath stays the current screen.
- Framework fragments such as
NavHostFragment.
A fragment is named by the first of these that it has:
- The route of the Navigation destination it shows, or else the destination's label. This needs Navigation 2.4 or later; with an older release the next two apply.
- The tag you gave it, for example with
replace(R.id.container, fragment, "settings"). Tags that libraries assign themselves, such as ViewPager2'sf0, are ignored. - Its class name.
Release builds that use R8 rename every fragment class that no layout or navigation graph
mentions. Fragments created in code, such as ViewPager2 pages or tabs you switch with fragment
transactions, then report names like a or c0 that change with every release. Give those
fragments a tag, or keep their class names with this rule in your app's proguard-rules.pro:
-keepnames class * extends androidx.fragment.app.FragmentCustom navigation
Call screen() yourself when your app drives navigation another way:
voidhash.screen("Onboarding step 2", mapOf("step" to 2))Event properties
| Property | Meaning |
|---|---|
$screen_name | Stable identity of the screen. Never contains ids. |
$screen_path | Concrete location, including route arguments. |
$screen_title | The activity title when one is set. |
$previous_screen_name | Screen the user came from. null on the first screen. |
$previous_screen_path | Path of the previous screen. |
$previous_screen_duration_ms | Milliseconds spent on the previous screen, including time in the background. |
$screen_source | android-activity, android-fragment, compose-navigation or manual. |
Options
VoidhashOptions(
screenTracking = ScreenTrackingOptions(
automatic = true,
fragments = false,
includeParams = false,
mapScreen = { view -> if (view.name.startsWith("Debug")) null else view },
),
)Set automatic to false to stop the SDK from recording activities as screens. includeParams
adds Compose route arguments to each event as $screen_params. It is off by default because
arguments often carry ids. mapScreen lets you rename a screen or drop it by returning null. If
it throws, the SDK drops that screen and reports a SCREEN_MAPPER_FAILED diagnostic instead of
letting the exception reach your activity or navigation callbacks.
Screen views can also be switched off per project without an app release from the events settings page in the dashboard.