LimeLink Android SDK
Target SDK Version: 0.4.0 | Min SDK: 24 | Compile SDK: 33 | Java bytecode: 8
LimeLink Android SDK provides App Link resolution, deferred deep links, and explicit link event tracking through public Kotlin and Java facades.
Version 0.4.0 is an intentional breaking migration from 0.3.x. Native initialization now requires the public Project UUID; API-key and public base-URL configuration have been removed.
View previous Android SDK releases
Before You Start
- Create or open a LimeLink Project.
- Register the Android Application in that Project.
- Copy the canonical Project UUID from Project settings. It is not a secret.
- Define the destination schemes and HTTPS hosts your app will allow before routing SDK results.
Installation
Add the public Maven repository in settings.gradle.kts:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://hellovelop.github.io/limelink-aos-sdk-binary/repository")
content { includeGroup("org.limelink") }
}
}
}
Add the released artifact:
dependencies {
implementation("org.limelink:limelink-aos-sdk:0.4.0")
}
No GitHub account, PAT, repository invitation, or Gradle credential is required. The SDK uses HTTPS and does not require global cleartext traffic.
Version 0.4.0 is consumer-verified with Gradle 7.3, AGP 7.0.4, JDK 11, Kotlin 1.6.10, and Java-only consumers. These are verified compatibility values, not universal minimum toolchain requirements.
Initialize with Kotlin
Initialize once from Application.onCreate() using the Project UUID:
import android.app.Application
import org.limelink.limelink_aos_sdk.LimeLinkSDK
import org.limelink.limelink_aos_sdk.config.LimeLinkConfig
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
val config = LimeLinkConfig.Builder(
"550e8400-e29b-41d4-a716-446655440000"
)
.setLogging(false)
.setDeferredDeeplinkEnabled(true)
.build()
LimeLinkSDK.init(this, config)
}
}
setDeferredDeeplinkEnabled(true) is the default. Production API routing is SDK-owned; setBaseUrl is not part of the 0.4.0 public API.
Manifest and App Links
Add Internet permission and configure the Activity that receives HTTPS Links:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application>
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop">
<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="links.example.com"
android:pathPrefix="/link/" />
</intent-filter>
</activity>
</application>
</manifest>
Publish the matching Digital Asset Links file for each hostname. Add separate filters for LimeLink subdomains and Project Custom Domains your app owns.
Receive Results
Register and retain a listener as early as possible after initialization:
private val linkListener = object : LimeLinkListener {
override fun onDeeplinkReceived(result: LimeLinkResult) {
val uri = result.resolvedUri
if (isAllowedDestination(uri)) {
navigateTo(uri)
}
}
override fun onDeferredDeepLinkNotFound() {
// Normal terminal result: continue the default launch flow.
}
override fun onDeeplinkError(error: LimeLinkError) {
showSafeFallback(error.code, error.message)
}
}
override fun onStart() {
super.onStart()
LimeLinkSDK.addLinkListener(linkListener)
}
override fun onStop() {
LimeLinkSDK.removeLinkListener(linkListener)
super.onStop()
}
Parse every returned URI and compare its scheme and host with an explicit app-owned allowlist. Avoid wildcard or string-prefix trust checks. The SDK returns a structurally valid server destination but does not authorize arbitrary app navigation.
A Universal result completed before listener registration is retained in process memory and replayed once to the first listener on the Android main thread. The Activity lifecycle example can miss an automatic deferred not-found produced before onStart(); apps that depend on that signal should register a long-lived listener immediately after SDK initialization.
V2 App Link Resolution
Normal Activity lifecycle integration handles incoming Intents. If the host must forward one explicitly:
LimeLinkSDK.handleUniversalLink(this, intent)
Lifecycle and explicit processing share an Intent-instance consumption guard, preventing the same Intent object from being delivered twice.
The SDK accepts an absolute HTTP(S) URL with a host and sends the exact OS-provided URL once as full_request_url. It does not extract or reconstruct a suffix, forward individual query keys, or merge the source query into the result. The server-returned uri is final.
Deferred Deep Links
When deferred handling is enabled, initialization checks Google Play Install Referrer for a LimeLink-issued RFC 4122 UUID v4 and performs the V2 lookup. Missing, malformed, or not-found referrers are normal no-match outcomes. Lookup or validation failure does not block app startup.
Request a fresh manual check when needed:
LimeLinkSDK.handleDeferredDeepLink(this) { result, error ->
when {
result != null && isAllowedDestination(result.resolvedUri) -> {
navigateTo(result.resolvedUri)
}
error != null -> showSafeFallback(error.code, error.message)
else -> Unit // Normal not-found outcome.
}
}
Manual matched, not-found, and failed outcomes are delivered exactly once to both completion and current listener, on the main thread. Their relative order is unspecified. Automatic not-found is sent once to the currently registered listener and is not guaranteed to replay after late registration. A manual check does not mutate automatic checked state.
Dynamic Lookup and Deferred lookup retry only HTTP 429 or 5xx, at most once with a bounded delay. Stats, transport, other 4xx, and invalid-response failures are not retried. Exhausted retryable Deferred failures can remain eligible on a later launch.
Error Codes
| Code | Meaning |
|---|---|
-1 | SDK is not initialized |
-2 | Transport or timeout failure |
-3 | Invalid successful response |
400 | Invalid backend request |
404 | Private not-found result |
| Other HTTP status | Backend response status |
Public network errors use bounded messages without request URLs, query values, ownership details, or native exceptions.
Explicit Stats
Dynamic Lookup already records routing attribution, so a successful lookup does not trigger a second automatic Stats request.
Use explicit tracking only when your app owns a separate view event:
LimeLinkSDK.trackLinkStatus(this, intent)
Stats is best effort, is not automatically retried, and cannot block Link delivery.
Java API
Java callers use the static LimeLinkJavaSDK facade rather than Kotlin object methods:
LimeLinkConfig config = new LimeLinkConfig.Builder(
"550e8400-e29b-41d4-a716-446655440000"
).build();
LimeLinkJavaSDK.initialize(getApplicationContext(), config, listener);
Call Java initialization on the Android main thread; otherwise it throws IllegalStateException.
LimeLinkJavaSDK.handleUniversalLink(this, getIntent());
LimeLinkJavaSDK.handleDeferredDeepLink(this, new DeferredDeepLinkCallback() {
@Override public void onMatched(LimeLinkResult result) {
if (isAllowedDestination(result.getResolvedUri())) {
navigateTo(result.getResolvedUri());
}
}
@Override public void onNotFound() {
// Continue normal launch.
}
@Override public void onFailed(LimeLinkError error) {
showSafeFallback(error.getCode(), error.getMessage());
}
});
Java callbacks are delivered on the main thread. LimeLinkJavaSDK.isInitialized() exposes read-only initialization state.
Public API
| API | Purpose |
|---|---|
LimeLinkSDK.init(app, config) | Initialize Kotlin facade with Project ID |
LimeLinkSDK.isInitialized | Read initialization state |
addLinkListener(listener) | Register result callbacks |
removeLinkListener(listener) | Remove callbacks |
handleUniversalLink(activity, intent) | Explicitly process one App Link Intent |
handleDeferredDeepLink(context, completion) | Perform a manual deferred check |
trackLinkStatus(context, intent) | Send an explicit best-effort view event |
LimeLinkJavaSDK.initialize(...) | Initialize the Java facade on main thread |
Public result fields include originalUrl, resolvedUri, queryParams, pathParams, and isDeferred. Raw referrer codes and internal transport data are not public API.
Migrating from 0.3.2
- Update the dependency to
org.limelink:limelink-aos-sdk:0.4.0and recompile. - Replace API-key configuration with
LimeLinkConfig.Builder(projectId)using the canonical Project UUID. - Remove
apiKey,getApiKey,baseUrl,getBaseUrl, andBuilder.setBaseUrlusage. - Continue using
LimeLinkSDKfor Kotlin andLimeLinkJavaSDKfor Java. - Treat the server-returned URI as final; do not reconstruct URLs or merge source query parameters.
- Update error handling for
-1,-2,-3, and actual backend HTTP statuses. - Expect bounded retry only for lookup
429/5xx, and fail-open Deferred startup behavior. - Apply a strict destination scheme/host allowlist before navigation.
- Do not send duplicate automatic Stats after successful lookup.
- Remove legacy
is_first_launch,EventType, compound referrer, andoriginal-urlrestoration assumptions.
Troubleshooting
Dependency cannot be resolved
Confirm the public repository URL, org.limelink content group, hyphenated artifact ID limelink-aos-sdk, and version 0.4.0. Refresh Gradle dependencies after changes.
Links are not delivered
- Confirm
LimeLinkSDK.init()runs once with a valid Project UUID. - Retain and register the listener.
- Verify App Links, Digital Asset Links, and
singleTophandling. - Do not recreate React Native initial URLs as new Intents.
- Apply destination allowlisting before navigation.