Android SDK
The Android SDK (com.tolinku.sdk) provides App Links handling, deferred deep linking, event tracking, referrals, and in-app messages for Android.
Installation
Section titled “Installation”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)User identification
Section titled “User identification”Tolinku.setUserId("user_123")
// Clear on logoutTolinku.setUserId(null)Event tracking
Section titled “Event tracking”// In a coroutine scope:Tolinku.track("custom.app_open")
Tolinku.track("custom.purchase", mapOf( "amount" to "29.99", "currency" to "USD"))
// Force flushTolinku.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) { }})Ecommerce tracking
Section titled “Ecommerce tracking”Track purchases, cart activity, and product events via Tolinku.ecommerce:
Tolinku.setUserId("user_123")
// Track a purchaseTolinku.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 eventsTolinku.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 ratingsTolinku.ecommerce.search(searchTerm = "shoes")Tolinku.ecommerce.rate(itemId = "sku_1", rating = 4.5, maxRating = 5.0)
// Force flushTolinku.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.
Handling App Links
Section titled “Handling App Links”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>Resolving an incoming link
Section titled “Resolving an incoming link”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/4821Nothing 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.
Deferred deep linking
Section titled “Deferred deep linking”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.
What it does, in order
Section titled “What it does, in order”- 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.
- 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.
Calling it once
Section titled “Calling it once”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.
Claiming with a token you already hold
Section titled “Claiming with a token you already hold”If you have a token from somewhere other than the Play referrer, claim it directly:
val link = Tolinku.deferred.claimByToken(token, appspaceId = "64f0a1b2c3d4e5f60718")Signals only
Section titled “Signals only”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)Counting taps that open your app directly
Section titled “Counting taps that open your app directly”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.
Referrals
Section titled “Referrals”val referrals = Tolinku.referrals
// Create a referral codeval result = referrals.create(userId = "user_123", userName = "Jane")println(result.referralCode) // "ABC123"println(result.referralUrl) // "https://myapp.tolinku.com/ref/ABC123"
// Look up a referralval 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 leaderboardval leaders = referrals.leaderboard(limit = 10)In-app messages
Section titled “In-app messages”// Show highest-priority messageTolinku.messages.show( context = this, trigger = "on_open", onAction = { action -> /* Handle CTA URL */ }, onDismiss = { /* Message dismissed */ })Messages are rendered in a WebView dialog.
Teardown
Section titled “Teardown”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.