Deep linking
Handle incoming deep links in your Android app with the Grovs SDK.
Grovs delivers each opened link to a listener in your launcher activity. Route the user from there.
Receiving deep link events
The listener gets the link, its custom payload and its tracking metadata:
Grovs.setOnDeeplinkReceivedListener(this) { deeplinkDetails ->
val link = deeplinkDetails.link
val payload = deeplinkDetails.data
val tracking = deeplinkDetails.tracking
payload?.get("screen")?.let { screen ->
navigateTo(screen as String)
}
}It runs on the main thread. It fires for links that open the app, links delivered while it runs, deferred links matched after a fresh install, and (since 3.1) tapped Grovs push campaigns. See What your app receives for the push case.
Using Kotlin Flow
With coroutines, observe the openedLinkDetails flow. It emits the same DeeplinkDetails, or null until the app is opened from a link:
import io.grovs.utils.flow
lifecycleScope.launch {
Grovs.Companion::openedLinkDetails.flow.collect { deeplinkDetails ->
deeplinkDetails?.let {
Log.d("Grovs", "Link: ${it.link}, data: ${it.data}")
}
}
}When the lookup fails
If the lookup fails (phone offline, backend unavailable), the SDK keeps the link and retries while the app stays open in the same session:
- after a short, widening wait
- when the app returns to the foreground
- as soon as the network comes back
The listener fires once the lookup succeeds. A link refused with a 4xx other than 429 isn't retried.
Clipboard-assisted deferred deep linking
When a copy-to-clipboard link opens in a browser, the preview page copies it to the clipboard before sending the visitor to Google Play. On first launch after install, if fingerprint matching found nothing, the SDK checks the clipboard and attributes the install deterministically. The deferred link arrives through the same listener as a fingerprint match.
No integration is needed. What to expect:
- Android 12+ shows the system "pasted from your clipboard" toast once, on that first launch.
- The check is skipped when the backend reports no recent clicks on your clipboard-enabled links (currently a 48-hour window), and when the clipboard holds no web URL. An organic install on a project with active clipboard links may still see the toast once.
- Only URLs on accepted Grovs link hosts are sent. Hosts are checked after the text is read, so a non-Grovs URL can trigger the toast with nothing sent.
If your links use a custom domain, list it so the SDK recognizes them:
Grovs.configure(
this,
"your-api-key",
useTestEnvironment = false,
baseURL = null,
autoTrackScreenViews = true,
clipboardDomains = listOf("links.yourdomain.com")
)Enable copy-to-clipboard per link with copyToClipboardAndroid, or set the project default in the dashboard. Flow, settings and limits: Copy to Clipboard.
Install attribution timing
Install attribution has one 25-second budget for the install-referrer lookup, fingerprint matching and the clipboard fallback. Lifecycle, purchase and custom events queued in that window are held so they credit the matched link. After the deadline they may upload unattributed; a later match still applies to events still queued.
A link that opened the app is attributed to new lifecycle and purchase events immediately and overrides a pending launch lookup. Custom events pick it up once the lookup completes.
Getting link details
Fetch details for a link generated in the current environment, by path:
// Using a callback
Grovs.linkDetails(path = "/my-link-path", lifecycleOwner = this) { details, error ->
details?.let { Log.d("Grovs", "Details: $it") }
error?.let { Log.e("Grovs", "Error: $it") }
}
// Using coroutines
lifecycleScope.launch {
try {
val details = Grovs.linkDetails(path = "/my-link-path")
} catch (e: GrovsException) {
Log.e("Grovs", "Error: ${e.message}")
}
}The callback runs on the main thread and is cancelled if lifecycleOwner is destroyed first. The coroutine form throws GrovsException on failure.