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

  1. Create or open a LimeLink Project.
  2. Register the Android Application in that Project.
  3. Copy the canonical Project UUID from Project settings. It is not a secret.
  4. 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.

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.

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.

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

CodeMeaning
-1SDK is not initialized
-2Transport or timeout failure
-3Invalid successful response
400Invalid backend request
404Private not-found result
Other HTTP statusBackend 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

APIPurpose
LimeLinkSDK.init(app, config)Initialize Kotlin facade with Project ID
LimeLinkSDK.isInitializedRead 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

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.

  1. Confirm LimeLinkSDK.init() runs once with a valid Project UUID.
  2. Retain and register the listener.
  3. Verify App Links, Digital Asset Links, and singleTop handling.
  4. Do not recreate React Native initial URLs as new Intents.
  5. Apply destination allowlisting before navigation.