Skip to main content
Documentation
SDKsAndroid

Android Deep Links

Handle Android App Links to route taps into the right screen, with cold start and warm start covered by the WarpLink SDK.

When a user taps a WarpLink URL and your app is installed, Android opens your Activity via App Links. The SDK resolves the URI into a WarpLinkDeepLink with the destination, custom parameters, and attribution data, then delivers it to the onLink callback you passed to configure().

configure() auto-registers ActivityLifecycleCallbacks, so cold start is automatic: a link that launches your app resolves and dispatches to onLink with no per-Activity code. Warm start (a link arriving while the app already runs) needs one line, because Android delivers it through onNewIntent. Both paths are gated by automaticDeepLinks, so opting out turns off the whole automatic model, not just cold start.

Set android:launchMode="singleTask" on the Activity so warm-start links reuse the running instance. Resolved links arrive in your onLink callback, always on the main thread.

Why singleTask Specifically

Android's default launch mode (standard) creates a brand new Activity instance for every Intent, including one carrying a WarpLink URL. If your launch Activity uses the default, a warm-start tap pushes a second instance onto the back stack instead of routing through the one already on screen, and onNewIntent never fires on the instance the user is looking at. singleTask collapses that: Android routes the intent to the existing instance and calls onNewIntent on it directly. singleTop also works if the Activity is already at the top of the stack, but singleTask is the safer default because it holds regardless of what else is on the back stack.

Cold Start vs Warm Start

ScenarioEntry PointBehavior
Cold startApp not running: system launches ActivityHandled automatically. The link resolves and onLink fires
Warm startApp in background: brought to foregroundonNewIntent() forwards the intent via WarpLink.onNewIntent, then onLink fires

Your onLink callback receives a WarpLinkDeepLink:

On a direct open, customParams holds the query parameters of the link's destination URL, including any parameters the link adds. A deferred match returns no custom parameters, whether the Play Install Referrer or the probabilistic match found it, so route a deferred install on deepLinkUrl or destination.

Manual Resolution (Advanced)

To drive resolution yourself, set automaticDeepLinks = false in WarpLinkOptions and call handleDeepLink with the URI from intent?.data:

Error Handling

Verify assetlinks.json

WarpLink generates the Digital Asset Links file automatically. Verify it:

To check any link domain, including a custom domain, paste it into our deep link checker. If you also host the file on a domain of your own, our assetlinks.json generator builds it from your package name and SHA-256 fingerprint.

Check App Links verification status:

If App Links aren't verified, try uninstalling and reinstalling the app. Android re-verifies assetlinks.json at install time.

Jetpack Compose Navigation

The examples above route through onLink and onNewIntent the same way regardless of UI toolkit. In a Compose app, the difference is only where you call your navigation function. Hoist a NavHostController above setContent (or use rememberNavController() and pass it into your WarpLinkOptions.onLink closure via a remember-captured reference) so the callback registered in onCreate() can reach a controller that Compose has not necessarily recomposed yet:

Because configure() typically runs in Application.onCreate(), before any Activity or Composable exists, a cold-start link can resolve before there is a NavHostController to route into. Buffer the pending destination in a small holder (a mutableStateOf<String?> at application scope, or a one-shot SharedFlow) and consume it once your NavHost has composed, rather than assuming the callback and the first composition happen in a fixed order.

ProGuard / R8

Nothing to add here beyond what ships with the SDK. consumer-rules.pro keeps the public API classes automatically, and the SDK does no reflection, so a minified release build behaves identically to a debug build for deep link resolution. If you see an obfuscated class name in a crash report that traces into WarpLink code, that is a sign your own -keep rules are too narrow, not that the SDK needs one.

Troubleshooting

InvalidUrl for a URL you expect to resolve. The SDK recognizes aplnk.to plus any domain declared through linkDomains, the app.warplink.DOMAINS manifest entry, or fetched from /sdk/validate after the SDK key validates. Declare your app's subdomain or custom domain locally so it is recognized from the first launch, online or offline.

Deep link opens the app but nothing navigates. Confirm onLink is actually wired in WarpLinkOptions and that automaticDeepLinks was not set to false without a corresponding manual handleDeepLink() call. Turn on debugLogging and filter Logcat for the WarpLink tag: a Resolving deep link: line with no matching Deep link resolved: line means the request is failing server-side (deleted or expired link), not a wiring problem in your Activity.

Works from adb shell am start but not from a real tap. This almost always means App Links verification itself is the problem, not the SDK. adb shell am start bypasses verification and hands the intent to your app directly, so it succeeds even when assetlinks.json would fail a real tap. Check adb shell pm get-app-links separately.

FAQ

Does onNewIntent need to run before super.onNewIntent(intent), or after? After, matching the pattern shown throughout this page. WarpLink.onNewIntent only reads the intent's data; it does not depend on anything super.onNewIntent does first.

What happens if I forget singleTask and rely only on the automatic model? Cold start still works, since it reads the launch intent directly. Warm start silently stops working: Android creates a fresh Activity instance instead of calling onNewIntent on the running one, so a tap while the app is open does nothing visible and no error is logged, because from the SDK's point of view no intent ever arrived.

Can I use a NavHost-per-feature-module architecture instead of a single Activity? Yes, as long as one Activity owns the App Links intent filter and forwards through onNewIntent. Multi-module Compose navigation graphs are orthogonal to how WarpLink receives the intent; only the Activity boundary matters to the SDK.

Does the SDK read the launch intent more than once? No. Cold-start resolution happens once, registered through ActivityLifecycleCallbacks when configure() runs. A subsequent call to getIntent() on the same Activity instance returns the same data, but the SDK does not re-resolve it; only a new intent delivered through onNewIntent triggers a new resolution.