Skip to content

Android SDK

The Android SDK (com.tolinku.sdk) provides App Links handling, deferred deep linking, event tracking, referrals, and in-app messages for Android.

Add the dependency to your build.gradle.kts:

dependencies {
implementation("com.tolinku:sdk:0.5.0")
}

Configure the SDK in your Application.onCreate():

import com.tolinku.sdk.Tolinku
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
Tolinku.configure(
apiKey = "tolk_pub_your_key",
context = this // enables lifecycle-aware auto-flushing
)
}
}

Optional parameters:

Tolinku.configure(
apiKey = "tolk_pub_your_key",
baseUrl = "https://your-app.tolinku.com",
context = this,
debug = true // enables debug logging
)
Tolinku.setUserId("user_123")
// Clear on logout
Tolinku.setUserId(null)
// In a coroutine scope:
Tolinku.track("custom.app_open")
Tolinku.track("custom.purchase", mapOf(
"amount" to "29.99",
"currency" to "USD"
))
// Force flush
Tolinku.analytics.flush()

For Java interop, use the callback-based wrappers:

Tolinku.analytics.trackAsync("custom.app_open", null, object : TolinkuCallback<Unit> {
override fun onSuccess(result: Unit) { }
override fun onError(error: TolinkuError) { }
})

Track purchases, cart activity, and product events via Tolinku.ecommerce:

Tolinku.setUserId("user_123")
// Track a purchase
Tolinku.ecommerce.purchase(
transactionId = "order_456",
revenue = BigDecimal("49.99"),
currency = "USD",
items = listOf(
TolinkuItem(itemId = "sku_1", itemName = "T-Shirt", price = BigDecimal("24.99"), quantity = 2)
)
)
// Track product views and cart events
Tolinku.ecommerce.viewItem(
items = listOf(TolinkuItem(itemId = "sku_1", itemName = "T-Shirt"))
)
Tolinku.ecommerce.addToCart(
items = listOf(TolinkuItem(itemId = "sku_1", quantity = 1))
)
Tolinku.ecommerce.beginCheckout()
// Search and ratings
Tolinku.ecommerce.search(searchTerm = "shoes")
Tolinku.ecommerce.rate(itemId = "sku_1", rating = 4.5, maxRating = 5.0)
// Force flush
Tolinku.ecommerce.flush()

Ecommerce events are batched (10 events or 5-second timer) and auto-flushed when the app goes to the background via ActivityLifecycleCallbacks. The SDK manages cart IDs automatically via SharedPreferences, clearing them after purchase. All money values use BigDecimal for precision.

Handle incoming App Links in your Activity:

class DeepLinkActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
intent.data?.let { uri ->
val path = uri.path // e.g. "/merchant/abc123"
val token = uri.lastPathSegment
// Navigate to the deep link destination
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent.data?.let { uri ->
// Handle deep link when activity is already running
}
}
}

Add the intent filter to your AndroidManifest.xml:

<activity android:name=".DeepLinkActivity">
<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="your-app.tolinku.com" />
</intent-filter>
</activity>

A link arrives as the URL that was tapped, exactly as it was written. That is enough while the URL is readable, but every route also has a short link, and a short link is the same route written as a code:

https://links.example.com/s7k2p9q/4821

Nothing in that URL says which route it is, and nothing on the device can work it out. Short links are what the dashboard’s copy button gives you and what a QR code carries, so your app will receive them whether or not you chose to share them.

links.resolve asks Tolinku and answers with the route, the token and the canonical path. A readable URL resolves to itself, so resolve every incoming link rather than trying to spot the short ones. It returns nothing rather than throwing when it cannot reach us, so your own handling stays the fallback.

suspend fun handle(url: String) {
val link = Tolinku.links.resolve(url)
val path = link?.deepLinkPath ?: Uri.parse(url).path
// path -> "/merchant/abc123", and link?.token -> "abc123"
route(path)
}

token saves you working out which segment it is, which the URL alone does not tell you when a route’s prefix places its token mid-path.

Recover the link a user tapped before they had your app, and route them to it on first launch.

val link = Tolinku.deferred.claimDeferredLink(
appspaceId = "64f0a1b2c3d4e5f60718",
context = applicationContext,
)
link?.deepLinkPath?.let { path ->
// Route to path, e.g. "/product/42"
}

Call it once, on the first launch after install. That is the whole integration: the SDK reads the Play Install Referrer itself, falls back to device signals if there is nothing there, and remembers that it asked.

  1. Play Install Referrer. A Tolinku link sends an Android visitor to the store with a token attached, and Play hands that token back on first launch. This names the exact click: no guessing, and it survives for days.
  2. Device signals, if there was no referrer, as on a sideloaded install. Timezone, language, screen size and pixel ratio are matched against what the landing page recorded. Probabilistic, and the window is short.

You do not choose between them and you do not add the Install Referrer library yourself. The SDK bundles it and declares Android as its only platform, so an iOS-only build never compiles it.

A claim is consumed the first time it succeeds. claimDeferredLink remembers that it asked, so calling it on every launch costs nothing after the first.

Only a real answer is remembered. “Nothing waiting for this device” counts, because no amount of asking will change it. A dropped request does not, so one bad connection does not spend the install’s only chance at attribution.

If you have a token from somewhere other than the Play referrer, claim it directly:

val link = Tolinku.deferred.claimByToken(token, appspaceId = "64f0a1b2c3d4e5f60718")

claimBySignals is still available and unchanged. It asks every time it is called and does no remembering, so use it only if you are doing that bookkeeping yourself:

val link = Tolinku.deferred.claimBySignals(
appspaceId = "64f0a1b2c3d4e5f60718",
context = this
)

A link that opens your app directly never reaches Tolinku, so the tap is not counted. Those taps are the ones from people who already have your app, so leaving them out makes a campaign aimed at existing customers look like it got no traffic.

trackLinkOpen reports one. Call it wherever your app receives an incoming link.

A link arrives in two places and both need it. One that launches your app cold arrives somewhere different from one tapped while the app is already running, and instrumenting only the second misses the more common case while appearing to work.

// Launched by a link, activity was not running.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
intent?.data?.let { uri ->
lifecycleScope.launch { Tolinku.trackLinkOpen(uri.toString()) }
}
}
// Tapped while the activity was already there.
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
intent.data?.let { uri ->
lifecycleScope.launch { Tolinku.trackLinkOpen(uri.toString()) }
}
}

Wiring both is safe: some link plugins hand the launching link to the listener as well, and the same link inside a few seconds is reported once rather than counted twice.

Only http and https links are reported. A custom scheme means Tolinku’s own hand-off page opened your app, and that tap was counted when the page was served. The call never throws and never blocks.

Whether these are recorded is an Appspace setting, and it decides the bill. See Attributing app opens.

val referrals = Tolinku.referrals
// Create a referral code
val result = referrals.create(userId = "user_123", userName = "Jane")
println(result.referralCode) // "ABC123"
println(result.referralUrl) // "https://myapp.tolinku.com/ref/ABC123"
// Look up a referral
val info = referrals.get(code = "ABC123")
// Link a referred user (status stays pending until reward milestone is reached)
referrals.complete(code = "ABC123", referredUserId = "user_456")
// Update milestone (completes the referral if it matches the reward milestone)
referrals.milestone(code = "ABC123", milestone = "first_purchase")
// Claim reward (after granting it in your system)
referrals.claimReward(code = "ABC123")
// Get leaderboard
val leaders = referrals.leaderboard(limit = 10)
// Show highest-priority message
Tolinku.messages.show(
context = this,
trigger = "on_open",
onAction = { action -> /* Handle CTA URL */ },
onDismiss = { /* Message dismissed */ }
)

Messages are rendered in a WebView dialog.

Tolinku.destroy()

This flushes queued events, cancels pending requests, and releases resources. Call configure again before using the SDK afterwards.

Tolinku.shutdown() is the same call under the name this SDK shipped with. It still works and is not deprecated. destroy() is the name every Tolinku SDK uses, so prefer it in new code.