Android SDK
Installation
Section titled “Installation”Add the Trace SDK to your module-level build.gradle.kts:
All Android apps need the core SDK:
dependencies { implementation("io.traceclick:trace-sdk:<version>")}If you use Jetpack Navigation3, add the Nav3 integration alongside the core SDK:
dependencies { implementation("io.traceclick:trace-sdk:<version>") implementation("io.traceclick:trace-sdk-nav3:<version>")}For Kotlin Multiplatform projects using JetBrains Compose Multiplatform Navigation3:
kotlin { sourceSets { commonMain.dependencies { implementation("io.traceclick:trace-sdk:<version>") implementation("io.traceclick:trace-sdk-nav3-kmp:<version>") } }}Use trace-sdk-nav3-kmp for KMP projects — it compiles against JetBrains Compose Multiplatform coordinates and works on both Android and iOS. For Android-only projects using AndroidX Navigation3, use trace-sdk-nav3 instead.
Initialization
Section titled “Initialization”Initialize Trace in your Application class. This must happen before any activity launches.
class MyApp : Application() { override fun onCreate() { super.onCreate() Trace.initialize( context = this, config = TraceConfig( apiKey = "tr_live_xxxxxxxxxxxx", hashSalt = "your_64_char_hex_salt", region = Region.US // or Region.EU ) ) }}The SDK automatically performs attribution on first launch — no additional call needed.
Handling deep links
Section titled “Handling deep links”Choose the approach that matches your app’s architecture.
Non-Compose (Activities / Fragments)
Section titled “Non-Compose (Activities / Fragments)”For apps that don’t use Jetpack Compose, set a listener and forward intents manually.
-
Set a deep link listener in your
Application.onCreate()(or wherever you initialize):Trace.setDeepLinkListener { deepLink ->// deepLink.path — e.g. "/product/123"// deepLink.params — e.g. {"color": "blue"}// deepLink.isDeferred — true if delivered via install attributionnavigateTo(deepLink.path, deepLink.params)} -
Forward intents in your launcher Activity so the SDK can process direct deep links:
class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)TraceAndroid.handleIntent(intent)// ... set up your UI}override fun onNewIntent(intent: Intent) {super.onNewIntent(intent)TraceAndroid.handleIntent(intent)}}
Compose (without Navigation3)
Section titled “Compose (without Navigation3)”For Compose apps that use their own navigation (or no navigation library), wrap your root composable with TraceProvider. It handles intent forwarding automatically — no need to call handleIntent in your Activity.
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { TraceProvider { App() } } } // No handleIntent needed — TraceProvider handles it.}Use rememberDeepLinkMapper to define how deep link paths map to your app’s routes or actions:
@Composablefun App() { val mapper = rememberDeepLinkMapper { route("/product/{id}") { params -> ProductRoute(id = params.require("id")) } route("/settings") { SettingsRoute } action("/claim/promo/{amount}") { params -> claimReward(params.require("amount")) } }
// Use the mapper to handle incoming deep links however your app navigates. val trace = LocalTrace.current val deepLink by trace.deepLink.collectAsStateWithLifecycle()
LaunchedEffect(deepLink) { val link = deepLink ?: return@LaunchedEffect when (val result = mapper(link)) { is DeepLinkResult.Navigate -> navigateTo(result.route) is DeepLinkResult.Action -> result.execute() null -> { /* unrecognized path */ } } trace.consumeDeepLink() }}Deferred deep links with auth gates
Section titled “Deferred deep links with auth gates”If your app requires login before navigating to a deep link destination, use TracePostAuthEffect to drain parked deferred deep links after authentication:
@Composablefun App() { val isLoggedIn by authViewModel.isLoggedIn.collectAsStateWithLifecycle()
TraceProvider { // Deep links that arrive before auth is ready are parked automatically. // This effect drains them once the user is logged in. TracePostAuthEffect( isAuthenticated = isLoggedIn, onDeepLink = { deepLink -> navigateTo(deepLink.path) } )
AppContent() }}Navigation3
Section titled “Navigation3”For apps using Jetpack Navigation3, the io.traceclick:trace-sdk-nav3 artifact provides a decorator that integrates directly with your NavDisplay back stack.
import com.trace.sdk.compose.TraceProviderimport com.trace.sdk.compose.rememberDeepLinkMapperimport com.trace.sdk.nav3.rememberTraceEntryDecoratorimport com.trace.sdk.nav3.navigateReplacing-
Define your route mapper with
rememberDeepLinkMapper:val mapper = rememberDeepLinkMapper {route("/product/{id}") { params ->ProductRoute(id = params.require("id"))}route("/invite/{code}") { params ->InviteRoute(code = params.require("code"))}route("/checkout") { params ->CheckoutRoute(promo = params["promo"])}action("/claim/promo/{amount}") { params ->claimReward(params.require("amount"))}} -
Create the entry decorator and wire it into
NavDisplay:@Composablefun AppNavigation(authViewModel: AuthViewModel) {val backStack = rememberNavBackStack(HomeRoute)val isLoggedIn by authViewModel.isLoggedIn.collectAsStateWithLifecycle()val mapper = rememberDeepLinkMapper {route("/product/{id}") { ProductRoute(id = it.require("id")) }route("/invite/{code}") { InviteRoute(code = it.require("code")) }route("/checkout") { CheckoutRoute(promo = it["promo"]) }action("/claim/promo/{amount}") { /* claimReward(it.require("amount")) */ }}TraceProvider {NavDisplay(backStack = backStack,onBack = { if (backStack.size > 1) backStack.removeLastOrNull() },entryDecorators = listOf(rememberSaveableStateHolderNavEntryDecorator(),rememberTraceEntryDecorator(backStack = backStack,routeMapper = mapper,authGate = { isLoggedIn }),),entryProvider = entryProvider {entry<HomeRoute> { HomeScreen(/* ... */) }entry<ProductRoute> { route -> ProductScreen(id = route.id) }// ... other entries})}}
Back stack extensions
Section titled “Back stack extensions”The Nav3 integration provides two back stack extensions in com.trace.sdk.nav3:
navigateReplacing(destination)— pushesdestination, replacing everything above the root. Used for deferred deep links so the user doesn’t back-navigate to a blank launch screen.navigateSingleTop(destination)— pushesdestinationonly if it isn’t already the top of the stack. Used for direct deep links when the app is already open.
The decorator uses these automatically, but you can also use them directly:
backStack.navigateReplacing(HomeRoute)Deep link params API
Section titled “Deep link params API”The params object in your route mapper provides typed accessors:
route("/product/{id}") { params -> val id = params.require("id") // throws if missing val color = params["color"] // nullable String val page = params.int("page") // Int? val premium = params.boolean("premium") // Boolean? ProductRoute(id, color, page, premium)}Attribution listener
Section titled “Attribution listener”To react to the attribution result directly (e.g. for analytics):
Trace.setAttributionListener { result -> when (result) { is AttributionResult.Attributed -> { analytics.track("install_attributed", mapOf( "method" to result.method, "campaign" to result.campaignId )) } is AttributionResult.Organic -> { analytics.track("install_organic") } is AttributionResult.Error -> { log.warn("Attribution failed: ${result.message}") } }}Event tracking
Section titled “Event tracking”Track post-install events to measure campaign effectiveness:
Trace.trackEvent( name = "purchase_completed", properties = mapOf( "value" to "49.99", "currency" to "USD", "product_id" to "SKU_123" ))See Event Tracking for best practices.
Privacy opt-out
Section titled “Privacy opt-out”Respect user consent by toggling data collection at runtime. The preference is persisted — you only need to call it once.
// After user opts out (e.g. in a settings screen)Trace.setEnabled(false)
// Re-enable laterTrace.setEnabled(true)
// Check current stateif (Trace.isEnabled) { /* ... */ }You can also disable collection at init time if you check consent before onCreate:
Trace.initialize( context = this, config = TraceConfig( apiKey = "tr_live_xxxxxxxxxxxx", hashSalt = "...", enabled = hasUserConsent() // false = no data collected ))See Configuration — Privacy opt-out for full details.
App Links setup
Section titled “App Links setup”To support direct deep links (not just deferred), two things are needed: an intent filter in your manifest, and a Digital Asset Links file on your Trace subdomain.
1. Register your cert fingerprint
Section titled “1. Register your cert fingerprint”Tell Trace your app’s signing certificate so it can serve the assetlinks.json file automatically:
# Get your SHA-256 fingerprintkeytool -list -v -keystore your-keystore.jks | grep SHA256
# Register it with Tracetrace apps update --android-cert-fingerprint "AA:BB:CC:..."2. Add the intent filter
Section titled “2. Add the intent filter”Add your Trace domain to AndroidManifest.xml:
<activity android:name=".MainActivity"> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="https" android:host="yourapp.traceclick.io" android:pathPrefix="/l/" /> </intent-filter></activity>Android will verify the domain by fetching https://yourapp.traceclick.io/.well-known/assetlinks.json — Trace handles this automatically.