Screen-View Tracking (Jetpack Compose)
Automatically track screen views whenever the visible destination changes in a Jetpack Compose Navigation graph.
Overview
Sees360 can automatically emit a system analytics event whenever the visible screen changes in a Jetpack Compose Navigation graph.
- Event name:
sees_screen_view(predefined/system) - Primary param:
screen_name— a stable screen name you define (see below)
The API lowercases all payload keys and values before persisting/exporting. Treat screen_name as a lowercase key (e.g., settings, home).
Requirements
- Jetpack Compose + Navigation Compose
- SDK initialized:
Sees360.initialize(context, apiKey, identifier) - (Optional) Usage Stats permission if you use usage-stats features. Screen-view events do not require it.
Quick start
- Initialize the SDK (after your permission flow, if any).
- Add
TrackScreenViews(navController) { destination -> screenKey }alongside yourNavHost. - Return stable, lowercase keys for each destination.
val nav = rememberNavController()
// Automatic screen-view tracking
TrackScreenViews(nav) { dest ->
when (dest.route) {
"home" -> "home"
"details" -> "details"
"settings" -> "settings"
"profile/{userId}" -> "profile" // strip args -> stable key
else -> "unknown"
}
}
NavHost(navController = nav, startDestination = "home") {
composable("home") { HomeScreen(onGoDetails = { nav.navigate("details") },
onGoSettings = { nav.navigate("settings") }) }
composable("details") { DetailsScreen(onBack = { nav.popBackStack() }) }
composable("settings") { SettingsScreen(onBack = { nav.popBackStack() }) }
}
Each destination change emits sees_screen_view with screen_name set to your chosen screen name. Because the backend downcases payload values, “Home” would be stored as “home” — emitting lowercase from the client keeps local logs consistent with stored data.
Initialization & lifecycle
Initialize once permission exists; if a user grants permission in system settings, auto-initialize on return.
private var sdkInitialized = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ensureInitialized()
setContent { /* NavHost + TrackScreenViews(...) */ }
}
override fun onResume() {
super.onResume()
if (!sdkInitialized) ensureInitialized()
}
private fun ensureInitialized() {
val identifier = runBlocking { dataStore.data.first()[IDENTIFIER_KEY] ?: generateAndStoreIdentifier() }
if (checkUsageStatsPermission(applicationContext)) {
Sees360.initialize(applicationContext, BuildConfig.API_KEY, identifier)
sdkInitialized = true
} else {
startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
sdkInitialized = false
}
}
Best practices
- Stable keys only. No IDs or ephemeral UI state in
screen_name(e.g., useprofile, notprofile:42). - One tracker per controller. If you have multiple NavControllers (tabs, nested graphs), attach a
TrackScreenViewsper controller. - Filter utility routes. To skip routes (dialogs/sheets), return
""or map them to a neutral key if your SDK ignores empty values. - Cross-platform alignment. Use the same keys on Android/iOS/Web for consistent dashboards.
Multiple graphs / tabs
TrackScreenViews(mainNav) {
when (it.route) { "feed" -> "feed" else -> "main:${it.route}".lowercase() }
}
TrackScreenViews(settingsNav) {
when (it.route) { "settings" -> "settings" else -> "settings:${it.route}".lowercase() }
}
Troubleshooting
- No events firing: Ensure
TrackScreenViewsobserves the sameNavControllerused byNavHost. - Duplicate events: Only one tracker per controller.
- Unexpected names: Routes with arguments must be matched by their pattern (e.g.,
"profile/{userId}"), not a concrete URI.
Event reference
- Name:
sees_screen_view - Payload (lowercased by backend):
screen_name: String — canonical screen key (lowercase)
{
"event_name": "sees_screen_view",
"payload": { "screen_name": "settings" }
}
Privacy & security
- Avoid logging sensitive data (API keys, identifiers).
- If your app supports analytics opt-out, render
TrackScreenViewsconditionally.
Do not include PII in screen_name.