Android

Gatepost for Android is a Kotlin library with a Gradle plugin for build integration. It supports Android 7.0 (API 24) and later, Views and Jetpack Compose, and adds about 410 KB to a release APK after R8.

Requirements

  • Android 7.0 (API 24) or later on the device. minSdk 21 projects compile, but the SDK does nothing below API 24.
  • Android Gradle Plugin 8.0 or later, Kotlin 1.9 or later. Java-only projects are supported.
  • Current SDK version: 2.9.0, plugin 2.9.0. See the changelog.

Installation

Artifacts are published to an authenticated Maven repository. The repository URL and credentials for your organisation are in the console under Project settings, SDKs. Keep them in ~/.gradle/gradle.properties or in CI secrets, not in the project.

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven {
            url = uri(providers.gradleProperty("gatepostMavenUrl").get())
            credentials {
                username = providers.gradleProperty("gatepostMavenUser").get()
                password = providers.gradleProperty("gatepostMavenToken").get()
            }
        }
    }
}

// app/build.gradle.kts
plugins {
    id("org.mustgate.gatepost") version "2.9.0"
}

dependencies {
    implementation("org.mustgate.gatepost:gatepost-android:2.9.0")
    implementation("org.mustgate.gatepost:gatepost-okhttp:2.9.0")   // optional
    implementation("org.mustgate.gatepost:gatepost-compose:2.9.0")  // optional
}

gatepost {
    projectKey.set("4f9c2e1b8a7d63e0")
    uploadMappings.set(true)
}

The plugin does three things: it injects the project key into the manifest so no code change is needed, it uploads the R8 mapping file for release builds, and it records the build number for release comparison. All three can be turned off individually.

Starting the SDK

With the plugin, the SDK starts itself from a ContentProvider before Application.onCreate and no code is required. To configure options in code, or if you do not use the plugin, disable the automatic start and call start yourself:

<!-- AndroidManifest.xml -->
<meta-data android:name="org.mustgate.gatepost.AUTO_START" android:value="false" />

// ShopApp.kt
import org.mustgate.gatepost.Gatepost

class ShopApp : Application() {
    override fun onCreate() {
        super.onCreate()
        Gatepost.start(this, "4f9c2e1b8a7d63e0") {
            environment = Environment.PRODUCTION
            sampleRate = 1.0
            trackAnr = true
        }
    }
}

The only permission the SDK needs is INTERNET, which the library manifest declares. ACCESS_NETWORK_STATE is optional and used to label sessions with the network type.

Configuration

OptionDefaultDescription
sampleRate1.0Fraction of sessions recorded, 0 to 1. Decided once per session. Remote.
environmentPRODUCTIONPRODUCTION, STAGING or DEVELOPMENT. Development sessions are kept 24 hours and are free.
trackScreenstrueAutomatic screens for Activities, Fragments and Compose navigation destinations.
trackNetworktrueInstrument HttpURLConnection. OkHttp needs the interceptor below.
trackCrashestrueUncaught exception handler and native signal handler for NDK crashes.
trackAnrtrueDetect ANRs with a main-thread watchdog and capture the thread dump.
trackFramestrueSlow and frozen frames through FrameMetrics (API 24 and later).
ignoredHostsemptySet()Hosts whose requests are not recorded. Remote.
uploadIntervalSeconds30Seconds between foreground uploads. Minimum 10. Remote.
maxBatchBytes262144Upper bound of one compressed upload.
releaseversionName (versionCode)Override the release identifier.
userIdnullOpaque user identifier, hashed on device before upload.

Network requests

OkHttp and Retrofit

Add the interceptor as the last application interceptor so it sees retries and redirects the way the app does:

val client = OkHttpClient.Builder()
    .addInterceptor(GatepostInterceptor())
    .build()

Retrofit and Ktor with the OkHttp engine are covered by the same interceptor. Route templates are taken from Retrofit annotations when available, otherwise numeric and UUID segments are replaced with placeholders.

Other clients

HttpURLConnection is instrumented automatically. Cronet and custom socket clients can report requests through Gatepost.network(...).

Screens

Activities and Fragments become screens named after their class. For Compose, wrap a destination or any composable that behaves like a screen:

@Composable
fun CheckoutRoute() {
    GatepostScreen("Checkout") {
        CheckoutContent()
    }
}

// Views
Gatepost.screen("Checkout")
Gatepost.screenReady("Checkout")   // when async content is visible

Rendering metrics for a screen run from the moment it is named until the first frame after layout, or until screenReady is called if you use it.

Custom traces

val trace = Gatepost.trace("checkout.submit")
trace["items"] = cart.size
trace["payment"] = method.name

val validation = trace.child("validate")
validate(cart)
validation.end()

try {
    api.submit(cart)
    trace.end()
} catch (e: IOException) {
    trace.end(e)
}

Kotlin coroutines are supported through trace.run { }, which ends the span when the block returns or throws.

Crashes and ANRs

JVM crashes are captured with an uncaught exception handler; native crashes with a signal handler when the gatepost-ndk artifact is added. ANRs are detected by a watchdog that samples the main thread every 500 ms and captures a thread dump when it has been blocked for more than 5 seconds. On Android 11 and later the SDK also reads ApplicationExitInfo on the next launch, so ANRs and out-of-memory kills the watchdog could not catch are still reported.

Each crash or ANR is linked to the session that preceded it, including screens visited and requests in flight.

R8 mapping upload

With uploadMappings.set(true) the plugin uploads mapping.txt after every release build. Without the plugin, run the CLI from CI:

gatepost-cli upload-mapping \
  --project-key 4f9c2e1b8a7d63e0 \
  --version-name 4.12.0 --version-code 41200 \
  --path app/build/outputs/mapping/release/mapping.txt

The SDK ships its own consumer ProGuard rules. If you maintain a custom configuration that strips them, keep:

-keep class org.mustgate.gatepost.** { *; }
-keepattributes LineNumberTable,SourceFile

Troubleshooting

  • No sessions. Set logLevel = LogLevel.DEBUG and filter logcat by Gatepost. A 401 means a wrong key. A network security configuration that pins certificates for all hosts will block the collector; add the collector host to the exceptions.
  • Duplicate screens. When a Fragment is hosted inside a tracked Activity, name the Fragment and disable Activity tracking for that host with @GatepostIgnore.
  • ANR watchdog on debuggers. The watchdog is paused while a debugger is attached, so breakpoints do not produce false ANRs.