Once TraceItX.start() has run, the SDK records each onResume, using the
Activity’s simple class name.
That is the whole automatic story — and on a single-Activity app it means one screen for the entire session, which is most modern apps. The rest is one line per screen.
Compose
Works with NavHost, state-based routing, Voyager and Decompose:
import com.traceitx.TXScreen
@Composable
fun CheckoutScreen(…) {
TXScreen(name = "Checkout")
…
}
In pagers and other keep-alive containers, off-screen pages stay composed — pass
active so the marker fires on selection rather than composition:
TXScreen(name = "Checkout", active = pagerState.currentPage == page)
Without it, a three-tab pager records all three screens once at startup and nothing thereafter.
Jetpack Navigation
One listener covers the whole app:
navController.addOnDestinationChangedListener { _, dest, _ ->
TraceItX.recordScreen(dest.route ?: dest.displayName)
}
Fragments and plain Views
override fun onResume() {
super.onResume()
TraceItX.recordScreen("Checkout")
}
How the chain behaves
- One global chain, chronological. Not per-back-stack — a tab switch
correctly reads
TabA → TabB. - The first screen emits nothing. There is no screen to come from yet. It
still becomes the
fromof the next transition. A → Ais suppressed. Configuration changes, recomposition, and returning from the background never produce a duplicate crumb — which matters on Android, where a rotation recreates the Activity.- Blank names are dropped, and
recordScreenis a no-op beforestart(). - Failures stay silent. Marker calls never throw into your navigation.
Naming screens
Route identifiers, never user content. OrderDetail, not
Order #4471 — jane@example.com. Names travel in the report and are shown
in triage. Screen names do pass through the breadcrumb buffer’s redaction, so an
obvious email or card number is caught — but redaction recognises shapes, not
meaning, and an order id or an internal username sails through. Do not rely on
it. Ids belong in the optional data map:
TraceItX.recordScreen("OrderDetail", mapOf("orderId" to orderId))
A NavDestination.route often contains argument placeholders rather than
values, which is why the listener above is safe by default — but check yours if
you build routes by string interpolation.
What lands in the envelope
{
"t": 1755500000000,
"seq": 42,
"kind": "navigation",
"message": "Cart → Checkout",
"data": {
"from": "Cart",
"to": "Checkout"
}
}
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Only one screen ever appears | Single-Activity app. Add TXScreen(), a destination listener, or recordScreen in onResume. |
| Every tab recorded once at startup, never again | Pages stay composed — pass active rather than relying on composition. |
| Screens repeat after a rotation | They should not — A → A is suppressed. If you see it, two destinations share a name. |
| Crumbs stop when the reporter opens | Expected. The trail is frozen so the reporter’s own UI never pollutes the evidence. |