Android SDK
Install and integrate the LinkTrail Android SDK — package io.linktrail, minSdk 26, on Maven Central — with configure, onLink, consent and the Install Referrer.
Binary AAR · package io.linktrail · entry point LinkTrail · minSdk 26 · artifact io.linktrail:sdk:0.0.5 on Maven Central.
Before this works, set up three things in the dashboard. Skip one and links fail silently — they open the browser instead of your app. Full flow: Quickstart.
- 1Register the app — Apps → Register app, with your package name and every SHA-256 signing fingerprint you ship under, including Play App Signing.
- 2Add a link domain — Domains. Or use your free
*.linktrail.iosubdomain. - 3Create an API key — Settings → API Keys. That is the
lt_live_…value below.
Install
The SDK is published to Maven Central, so no custom repository is needed — just add the dependency:
dependencies {
implementation("io.linktrail:sdk:0.0.5")
}mavenCentral() is already in the default repositories of every Android project, which also resolves the SDK's transitive dependencies (coroutines, Play Install Referrer, App Set ID). Keep google() alongside it.
Nothing else to configure: the INTERNET permission merges in from the SDK's manifest, and consumer ProGuard/R8 rules are bundled in the AAR — no app-side keep rules needed.
Integrate
import io.linktrail.LinkTrail
// In Application.onCreate(). The API key is required — a blank key throws.
LinkTrail.configure(context = this, apiKey = "lt_live_...")
// One hook — fires for deferred (first-launch) AND re-engagement links:
LinkTrail.shared?.onLink { link, source ->
router.route(link.path, link.customData)
}
LinkTrail.shared?.onError { error -> /* e.g. LinkTrailError.InvalidApiKey */ }Consent
Consent gating is on by default (requireConsent = true) and is deny-by-default. A fresh install is held until you make a consent decision — nothing is sent until you call setConsent(true) or setConsent(false), and only then does the deferred link route: grant attributes it, deny routes it without recording. Re-engagement links (an already-installed app opening a link) route regardless of consent — that only decides whether the open is recorded. Call setConsent once your consent UI resolves:
// Grant → releases the held install as an attributed install + flushes queued events:
LinkTrail.shared?.setConsent(true)
// Deny → still releases the install to route the deep link, but records nothing; clears the queue:
LinkTrail.shared?.setConsent(false)
// To track at init with no gate, opt out:
LinkTrail.configure(context = this, apiKey = "lt_live_...",
options = LinkTrailOptions(requireConsent = false))Forward incoming links from your Activity — both entry points:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LinkTrail.shared?.handleDeepLink(intent?.data)
}
override fun onNewIntent(intent: Intent) { // app already running
super.onNewIntent(intent)
setIntent(intent) // keep getIntent() current
LinkTrail.shared?.handleDeepLink(intent.data)
}Every callback API also has a coroutine suspend twin (trackInstallAsync, handleDeepLinkAsync, trackEventAsync). Callbacks are delivered on the main thread.
More
// Custom + revenue events:
LinkTrail.shared?.trackEvent("purchase", value = 59.99, currency = "USD")
// Cached results:
val attribution = LinkTrail.shared?.lastAttribution
val lastLink = LinkTrail.shared?.lastDeepLink
// Attribution hook (fires when an install is attributed):
LinkTrail.shared?.onAttribution { attribution -> /* … */ }
// Defer the install call entirely (send it yourself later):
LinkTrail.configure(context = this, apiKey = "lt_live_...",
options = LinkTrailOptions(autoTrackInstall = false))
LinkTrail.shared?.trackInstall()Options & defaults
| LinkTrailOptions field | Default | Notes |
|---|---|---|
| requireConsent | true | Deny-by-default: the deferred install is held until setConsent(true/false), then routes (grant attributes, deny routes-only); re-engagement routes regardless |
| autoTrackInstall | true | false = defer the install call entirely; call trackInstall() yourself |
| logEnabled | false | Master switch for SDK logging (tag: LinkTrail) |
| logLevel | INFO | DEBUG · INFO · WARNING · ERROR · NONE |
| requestTimeoutMillis | 15 000 | Per-request network timeout |
| retryPolicy | DEFAULT | 3 attempts, 500 ms base / 8 s max backoff; DISABLED turns retries off |
| linkDomains | empty (all hosts) | Exact host or subdomain match; see warning under Deep-link setup |
Errors are a sealed LinkTrailError: MissingApiKey (thrown synchronously by configure), InvalidApiKey (the backend rejected the key — arrives via onError, not from configure), NotALinkTrailUrl, InvalidUrl, Server, Transport, Decoding, EmptyResponse. Under the hood the SDK also queues failed events offline and retries them after the next successful install call, and handles the Play Install Referrer and App Set ID automatically — there's no API to call. (App Tracking Transparency / SKAdNetwork are iOS-only and have no Android equivalent.)
API surface
| API | Purpose |
|---|---|
| configure(context, apiKey, options) | Initialize; deferred install held until a setConsent decision, then routes (re-engagement routes regardless) |
| setConsent(granted) | Grant or revoke the consent gate (see Consent above) |
| onLink { link, source } | Deep link delivered (deferred + re-engagement) |
| onAttribution { attribution } | Install attribution result |
| onError { error } | Failures (invalid key, network…) |
| handleDeepLink(uri) | Forward App Links / custom schemes |
| trackEvent(name, value, currency) | Custom + revenue events |
| trackInstall(force) | Manual install track (with autoTrackInstall = false) |
| lastAttribution / lastDeepLink | Cached last results |
| trackInstallAsync / handleDeepLinkAsync / trackEventAsync | Coroutine suspend twins |
Deep-link setup
Declare your App Links host (plus an optional custom scheme) in the manifest:
<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.linktrail.io" />
</intent-filter>The assetlinks.json file is generated and hosted by LinkTrail — it's built from the package name + SHA-256 fingerprints you registered in the dashboard (to change fingerprints, edit the app in the dashboard, not a file). Links opening the browser or Play Store instead of your app? That's almost always App Links verification — see Troubleshooting. And if you set linkDomains, list every link host: re-engagement opens only route for listed hosts, while deferred install links still route — so the bug hides on fresh installs.
Example app: KickFlip — a Jetpack Compose storefront in example/ wired end to end. cd example && ./gradlew :app:installDebug, with your API key in example/local.properties as linktrail.apiKey=lt_live_… (without a key, the built-in deep-link simulator still works offline).