Integration

Product analytics for Android

Pug ships a native Kotlin SDK for Android, callable from Java. Initialize it once in Application.onCreate and app opens, screen views, and installs are tracked from there; typed methods cover the well-known events, and anything else goes through Pug.track.

Pug Android SDK: one SDK for Android sh.pug:android Open source, AGPL-3.0

Setup

Add Pug to Android

Four steps, start to first insight. Everything below uses the Android SDK exactly as it ships.

  1. Add the dependency

    The SDK is on Maven Central and supports minSdk 21. Its only runtime dependencies are kotlin-stdlib and kotlinx-coroutines-core (no AndroidX, OkHttp, or protobuf runtime), it declares only the INTERNET permission, and it needs no R8 or ProGuard rules.

    app/build.gradle.kts
    dependencies {
        implementation("sh.pug:android:0.1.0")
    }
  2. Initialize in Application.onCreate()

    Call Pug.init once in your Application subclass (registered as android:name in the manifest), so the SDK sees the first screen and app_open of every launch. It does no disk or network work on the calling thread, so it is safe on the main thread under StrictMode. It throws IllegalArgumentException only for a blank project id, a key that is not the project’s public key (pub_…), or an endpoint that is not an http(s) URL.

    App.kt
    import android.app.Application
    import sh.pug.android.Options
    import sh.pug.android.Pug
    
    class App : Application() {
        override fun onCreate() {
            super.onCreate()
            Pug.init(this, "YOUR_PROJECT_ID", Options("pub_YOUR_PUBLIC_KEY"))
        }
    }
  3. Name the screens that are not activities

    Each activity that resumes on a different screen is a screen_view, named after its class. Composables and fragments are not activities, so name them with Pug.screen: it tracks a screen_view unless that screen is already current, and later events carry it as $url, with the one before as $referrer. In a single-activity app, also set autoScreenViews = false in Options, so the screens are only the ones you name.

    AppNavHost.kt
    // Navigation Compose: name each destination as it shows
    val route = navController.currentBackStackEntryAsState().value?.destination?.route
    LaunchedEffect(route) { route?.let { Pug.screen(it) } }
    
    // Or wherever a screen appears, such as a fragment's onResume():
    Pug.screen("Settings")
  4. Identify people and track events

    identify() links the events tracked anonymously so far to your user id, with optional traits, and reset() on logout starts a new anonymous id and session. The generated Pug.track namespace has a method per well-known event with named arguments; anything custom goes through Pug.track(kind, properties). Every call returns at once, from any thread, and never throws.

    Checkout.kt
    Pug.identify("user_123", mapOf("plan" to "pro"))
    
    // Typed method for a well-known event:
    Pug.track.purchase(productId = "sku-1", amount = 99.50, currency = "USD")
    
    // Anything else, by name:
    Pug.track("tutorial_finished", mapOf("stepCount" to 4))
    
    // On logout:
    Pug.reset()
Framework notes

What to watch for in Android

The specifics that decide whether the numbers you see are the numbers that happened.

One activity means one automatic screen

Automatic screen views follow activities, so a single-activity Compose app would report one screen for everything, and a resumed activity would replace the screen you named. Turn autoScreenViews off there and call Pug.screen for each destination.

Consent is granted by default

To wait for a banner, start with defaultTrackingConsent = TrackingConsent.DENIED. While denied, events and identify calls are dropped rather than queued; the choice is saved across restarts, and an app_install, app_open, or screen view that consent held back at launch is tracked when the user opts in.

Lifecycle and installs are automatic

app_open fires when an activity starts while none was started, and app_close when the last one stops, but not for a rotation. app_install carries installSource, the package of the store that installed the app, such as com.android.vending for Google Play.

Built for StrictMode and bad networks

Sockets carry a TrafficStats tag, so detectUntaggedSockets stays quiet. Events queue in noBackupFilesDir, survive restarts, go out in batches, and back off for up to five minutes when a request fails for a reason that may pass.

What you get

Product analytics in your Android app

  • Auto-tracking from init()

    app_open and app_close as the app comes and goes, a screen_view each time an activity resumes on a new screen, and app_install or app_update on the first launch of each version.

  • Typed well-known events

    Pug.track.purchase(…), Pug.track.search(…) and the rest are generated from the event schemas with named arguments; anything custom goes through Pug.track(kind, properties).

  • Consent that survives restarts

    Start denied, opt in or out from your own banner, and the choice is saved. What consent held back at launch is tracked once the user says yes.

  • The same insights as web

    Trends, funnels, retention, flows, and unified profiles: one project can hold your Android app, your website, and your backend.

FAQ

Pug + Android: common questions

Which Android versions and languages does it support?

Android 5.0 and up (minSdk 21), from Kotlin or Java. Java gets Options.Builder and a class per well-known event with properties, such as new PurchaseEvent().amount(89), through the same Pug entry point.

Does it need AndroidX, OkHttp, or Jetpack Compose?

No. Its only runtime dependencies are kotlin-stdlib and kotlinx-coroutines-core. It talks to the API over HttpURLConnection with its own protobuf encoding, declares only the INTERNET permission, and needs no R8 or ProGuard rules.

Why do I see one screen in my Compose app?

Automatic screen views come from activities, and a single-activity app has one. Set autoScreenViews = false and call Pug.screen for each destination, for example from the current back stack entry’s route.

Does it track before the user consents?

Yes: consent is granted by default, and there is no cookieless mode on Android. To wait for a choice, start with defaultTrackingConsent = TrackingConsent.DENIED; events are dropped until optInTracking(), and the launch events consent held back are tracked then.

Does the AGPL apply to my app?

No. The SDK you ship in your app is MIT-licensed; AGPL-3.0 covers the Pug server. Self-host it and point the SDK there with endpoint in Options, or use Pug Cloud, free up to 100k events a month.

Can one project hold my Android app and my website?

Yes. Call identify() with the same user id from Android, the Web SDK, and your server, and the timelines resolve to one person, so a funnel can start on your site and finish in the app.

Add Pug to your Android app.

Open-source product analytics with unified profiles. Self-host under AGPL-3.0, or use Pug Cloud, free up to 100k events a month.

Questions? Email hello@pug.sh