Integration¶
Once you're capturing traffic and events, open the inspector.
Everything on this page needs snoop-ui
SnoopScreen and the Snoop entry points live in snoop-ui, the only artifact that pulls in
Compose — declare it as shown in Getting started. The
web viewer needs none of it.
Embed the inspector (common)¶
SnoopScreen is a Compose Multiplatform composable — host it anywhere in your debug UI (a dev menu,
a dedicated tab, behind a gesture):
SnoopScreen()
// or sized by a modifier:
SnoopScreen(modifier = Modifier.fillMaxSize())
SnoopScreen insets its own content against the status bar, the navigation bar, display cutouts and
the keyboard, while painting its background edge to edge behind them — so it looks right whether or
not the host app is edge-to-edge.
Group the timeline by screen¶
The timeline collapses into per-screen sections opened by an analytics event — screen_view
labelled with screen_name out of the box. It is set where events are captured, on the analytics
logger, and every viewer groups by what capture stamped:
val snoop = SnoopAnalytics {
groupBy("page_shown", "page")
// groupBy(null) // no grouping at all
}
See Grouping for the details — including that it applies to events captured from then on, and that the Group chip is hidden in a build that captures no analytics.
Android¶
Snoop exposes two extra entry points that take a Context:
Snoop.open(context) // launch the inspector as its own Activity
Snoop.showNotification(context) // post a live, ongoing notification
Snoop.dismissNotification(context) // remove it
Where the Activity lands, and its label¶
Snoop.open(context) and the notification both stack SnoopActivity on top of your app's task,
not in one of its own: launchMode="singleTask" picks a task by taskAffinity, which defaults to
the host's package. So Back returns to the screen you came from, and the multitasking view keeps
showing a single card — your app's — labelled by your launcher activity.
That means android:label="Snoop" on SnoopActivity is normally invisible. It only surfaces when
Snoop starts a task of its own, which happens if you tap the ongoing notification after the host
task is gone.
If you would rather have the inspector as a separate card — handy for putting your app and the
timeline side by side in split screen — give it its own affinity from
src/debug/AndroidManifest.xml. Debug, because SnoopActivity only exists there; the release
no-op ships no manifest and no such class:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<activity
android:name="com.snoop.ui.SnoopActivity"
android:taskAffinity="${applicationId}.snoop"
android:label="Patata Inspector"
tools:replace="android:label" />
</application>
</manifest>
tools:replace is what settles the conflict on android:label: without it the merger sees two
different values for the same attribute and fails the build. taskAffinity needs no tools:replace
because the library never sets it. The rest — exported, launchMode, theme — is left out on
purpose; it merges in from the library.
iOS¶
Snoop mirrors the Android object without a Context:
Snoop.showNotification() // request alert permission inline, then keep it in sync
Snoop.dismissNotification() // cancel and remove pending + delivered
showNotification() asks for alert permission the first time via UNUserNotificationCenter
(idempotent; a denial is a silent no-op). It installs a delegate that presents Snoop's own
notification as a foreground banner while forwarding everything else to whatever delegate your app
already set. Each capture republishes a single notification (fixed identifier), so it updates in
place instead of stacking.
The inspector itself is the shared SnoopScreen — embed it in your SwiftUI/Compose host as above.
Web viewer¶
For inspecting from a desktop browser, start the embedded server:
val url = SnoopWebServer.start() // loopback, port 9394; returns the URL to open
SnoopWebServer.stop()
It streams the same timeline over SSE and runs on Android and iOS. See Web viewer for bind modes, LAN access and tokens.