LimeLink Android SDK

SDK Version: 0.1.0 | Min SDK: 24 | Compile SDK: 35


Getting Started

Requirements

ItemRequired Version
compileSdk35
minSdk24
Kotlin2.0.0
AGP8.7.0
JDK17

Step 1: Add the JitPack Repository

Add JitPack to your project settings.gradle:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

Step 2: Add the Dependency

Add the SDK dependency to your module build.gradle:

dependencies {
    implementation 'com.github.hellovelope:limelink-aos-sdk:0.1.0'
}

Check the latest version on the JitPack page.


SDK Initialization

Initialize the SDK once in your Application class's onCreate():

import org.limelink.limelink_aos_sdk.LimeLinkConfig
import org.limelink.limelink_aos_sdk.LimeLinkSDK

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        val config = LimeLinkConfig.Builder("YOUR_API_KEY")
            .setLogging(true)                    // Debug logs (default: false)
            .setDeferredDeeplinkEnabled(true)     // Auto deferred deeplink check (default: true)
            // .setBaseUrl("https://custom.api.com/")  // Custom server (default: https://limelink.org/)
            .build()

        LimeLinkSDK.init(this, config)
    }
}

Config Options

ParameterTypeDefaultDescription
apiKeyString(required)LimeLink API key
baseUrlStringhttps://limelink.org/API server URL
loggingEnabledBooleanfalseEnable SDK internal debug logs
deferredDeeplinkEnabledBooleantrueAuto-check deferred deeplink on first install

Don't forget to register your Application class in the AndroidManifest.xml:

<application
    android:name=".MyApp"
    ...>
</application>

AndroidManifest Configuration

<!-- Internet permission -->
<uses-permission android:name="android.permission.INTERNET" />

<application
    android:name=".MyApp"
    android:networkSecurityConfig="@xml/network_security_config"
    ...>

    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:launchMode="singleTop">

        <!-- Universal Link (subdomain pattern) -->
        <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="*.limelink.org"
                android:pathPrefix="/link/" />
        </intent-filter>

        <!-- Legacy Deeplink -->
        <intent-filter>
            <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="deep.limelink.org" />
        </intent-filter>
    </activity>
</application>

android:launchMode="singleTop" is required so that new links are received via onNewIntent when the Activity is already running.

Network Security Config

Create res/xml/network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">limelink.org</domain>
    </domain-config>
</network-security-config>

After calling init(), the SDK automatically detects Activity lifecycle events. Simply register a listener to receive deep link results.

import org.limelink.limelink_aos_sdk.LimeLinkSDK
import org.limelink.limelink_aos_sdk.listener.LimeLinkListener
import org.limelink.limelink_aos_sdk.response.LimeLinkResult
import org.limelink.limelink_aos_sdk.response.LimeLinkError

class MainActivity : ComponentActivity() {

    private val linkListener = object : LimeLinkListener {
        override fun onDeeplinkReceived(result: LimeLinkResult) {
            val url = result.resolvedUri       // Final URI resolved by API
            val isDeferred = result.isDeferred // Whether this is a deferred deeplink
            val query = result.queryParams     // Query parameter map
            val mainPath = result.pathParams.mainPath
            val subPath = result.pathParams.subPath

            // Navigate to appropriate screen
            navigateTo(mainPath, query)
        }

        override fun onDeeplinkError(error: LimeLinkError) {
            Log.e("Deeplink", "[${error.code}] ${error.message}")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        LimeLinkSDK.addLinkListener(linkListener)
    }

    override fun onDestroy() {
        super.onDestroy()
        LimeLinkSDK.removeLinkListener(linkListener)
    }
}

LimeLinkResult Fields

FieldTypeDescription
originalUrlString?Original URL
resolvedUriUri?Final redirect URI resolved by the API
queryParamsMap<String, String>URL query parameters
pathParamsPathParamResponsePath parameters (mainPath, subPath)
isDeferredBooleanWhether this is a deferred deeplink (default false)
referrerInfoReferrerInfo?Install Referrer info (available when deferred)

LimeLinkError Fields

FieldTypeDescription
codeIntError code
messageStringError message
exceptionException?Underlying exception (if any)
override fun onDeeplinkReceived(result: LimeLinkResult) {
    if (result.isDeferred) {
        // App launched for the first time after install — restored link from before installation
        val referrer = result.referrerInfo
        Log.d("Deferred", "referrer url: ${referrer?.limeLinkUrl}")
        Log.d("Deferred", "query params: ${referrer?.limeLinkDetail?.queryParams}")
    } else {
        // Universal Link clicked while app is running
    }
}

Subdomain Pattern (Primary)

https://{subdomain}.limelink.org/link/{linkSuffix}
https://{subdomain}.limelink.org/link/{linkSuffix}?key1=val1&key2=val2

Flow:

  1. User clicks a URL like https://abc.limelink.org/link/promo?campaign=summer
  2. SDK extracts the subdomain (abc) and link suffix (promo) from the URL
  3. SDK extracts the full original URL (including query parameters) as full_request_url
  4. SDK fetches headers from https://abc.limelink.org for additional context
  5. SDK calls GET /api/v1/app/dynamic_link/promo?full_request_url={encoded_url} with collected headers
  6. API returns a uri value, which the SDK delivers to the registered listener
  7. App navigates to the resolved URI

Direct Access Pattern

https://limelink.org/api/v1/app/dynamic_link/{suffix}
https://limelink.org/universal-link/app/dynamic_link/{suffix}

When directly accessing the API URL, the SDK makes a direct API request and returns the uri value via the listener.

https://deep.limelink.org/{path}

For backward compatibility, the SDK also supports the legacy deeplink format. The SDK calls https://deep.limelink.org/link with query parameters (subdomain, path, platform=android) and returns the deeplinkUrl from the response.

Query String Handling

All query parameters in the original URL are preserved and forwarded to the API. For example:

Original: https://abc.limelink.org/link/test?campaign=summer&source=email
API call: GET /api/v1/app/dynamic_link/test?full_request_url=https://abc.limelink.org/link/test?campaign=summer&source=email

Deferred Deep Link allows you to restore a link that a user clicked before installing the app. When the user installs and opens the app for the first time, the SDK automatically retrieves the original deep link.

Use Cases

Automatic Mode (Default)

When deferredDeeplinkEnabled = true (the default), the SDK automatically checks for deferred deep links at init() time on first launch. First-launch detection is managed internally via SharedPreferences.

// Just call init() in Application.onCreate() — automatic handling
LimeLinkSDK.init(this, config)

// In your listener, check isDeferred to distinguish
LimeLinkSDK.addLinkListener(object : LimeLinkListener {
    override fun onDeeplinkReceived(result: LimeLinkResult) {
        if (result.isDeferred) {
            // Deep link restored from Install Referrer
            navigateToContent(result.resolvedUri)
        }
    }
})

Manual Invocation

You can also manually trigger a deferred deep link check at a specific point:

LimeLinkSDK.checkDeferredDeeplink(context) { result ->
    if (result != null) {
        Log.d("Deferred", "Found: ${result.originalUrl}")
    }
}

If you have the suffix from your own Install Referrer handling:

LimeLinkSDK.handleDeferredDeepLink(
    suffix = "campaign-xyz",
    fullRequestUrl = "https://abc.limelink.org/link/campaign-xyz"
) { resolvedUri ->
    resolvedUri?.let { navigateTo(it) }
}

Flow Diagram

1. User clicks a link before installing the app
       ↓
2. Link redirects to Play Store with referrer parameters
       ↓
3. User installs and opens the app
       ↓
4. SDK init() detects first launch via SharedPreferences
       ↓
5. SDK retrieves suffix from Install Referrer API
       ↓
6. SDK calls API: GET /api/v1/app/dynamic_link/{suffix}?event_type=setup
       ↓
7. API returns the resolved URI
       ↓
8. Listener receives LimeLinkResult with isDeferred=true

Install Referrer

Retrieve app installation source information through the Install Referrer API.

Basic Usage

LimeLinkSDK.getInstallReferrer(context) { referrerInfo ->
    if (referrerInfo != null) {
        Log.d("Referrer", "referrerUrl: ${referrerInfo.referrerUrl}")
        Log.d("Referrer", "limeLinkUrl: ${referrerInfo.limeLinkUrl}")
        Log.d("Referrer", "clickTimestamp: ${referrerInfo.clickTimestamp}")
        Log.d("Referrer", "installTimestamp: ${referrerInfo.installTimestamp}")
    }
}

ReferrerInfo Fields

FieldTypeDescription
referrerUrlString?Raw referrer string from Play Store
clickTimestampLongAd click time (in seconds)
installTimestampLongApp install start time (in seconds)
limeLinkUrlString?LimeLink URL extracted from referrer
limeLinkDetailLimeLinkUrl?Structured details of the LimeLink URL (v0.1.0+)

LimeLinkUrl Detailed Information

Use the limeLinkDetail field to access structured information from the referrer URL:

LimeLinkSDK.getInstallReferrer(context) { referrerInfo ->
    val detail = referrerInfo?.limeLinkDetail
    if (detail != null) {
        Log.d("Detail", "url: ${detail.url}")               // URL without query string
        Log.d("Detail", "fullUrl: ${detail.fullUrl}")        // Full URL with query string
        Log.d("Detail", "queryString: ${detail.queryString}")// Raw query string
        Log.d("Detail", "queryParams: ${detail.queryParams}")// Query parameter map
        Log.d("Detail", "referrer: ${detail.referrer}")      // Raw referrer string

        // Example: https://abc.limelink.org/link/test?utm_source=kakao&campaign=summer
        // detail.url         = "https://abc.limelink.org/link/test"
        // detail.fullUrl     = "https://abc.limelink.org/link/test?utm_source=kakao&campaign=summer"
        // detail.queryString = "utm_source=kakao&campaign=summer"
        // detail.queryParams = {utm_source=kakao, campaign=summer}
    }
}

LimeLinkUrl Fields

FieldTypeDescription
referrerStringFull raw referrer string
urlStringURL without query string
fullUrlStringFull URL including query string
queryStringString?Query string (part after ?, null if none)
queryParamsMap<String, String>Query parameter map

API Reference

MethodDescription
LimeLinkSDK.init(app, config)Initialize SDK (call once at app start)
LimeLinkSDK.addLinkListener(listener)Register a deep link event listener
LimeLinkSDK.removeLinkListener(listener)Remove a listener
LimeLinkSDK.checkDeferredDeeplink(context, callback?)Check deferred deeplink (auto-called during init)
LimeLinkSDK.handleDeferredDeepLink(suffix, fullRequestUrl?, callback?)Manually process a deferred deep link
LimeLinkSDK.getInstallReferrer(context, callback)Retrieve Install Referrer information
LimeLinkSDK.isUniversalLink(intent)Check if intent contains a Universal Link

Data Classes

ClassDescription
LimeLinkConfigSDK configuration (Builder pattern)
LimeLinkResultDeep link processing result
LimeLinkErrorError information (code, message, exception?)
ReferrerInfoInstall Referrer information
LimeLinkUrlReferrer URL detailed structure (v0.1.0+)
PathParamResponsePath parameters (mainPath, subPath?)

ProGuard / R8

The SDK already includes ProGuard rules, so no additional configuration is needed.

Included rules:

-keep class org.limelink.limelink_aos_sdk.** { *; }

If you need to add them explicitly:

-keep class org.limelink.limelink_aos_sdk.** { *; }

Troubleshooting

"Unresolved reference: LimeLinkSDK"

# If using JitPack — verify JitPack repository is added to settings.gradle
# Refresh dependency cache
./gradlew clean --refresh-dependencies
  1. Verify intent-filter is configured in AndroidManifest.xml
  2. Confirm android:launchMode="singleTop" is set
  3. Ensure LimeLinkSDK.init() has been called
  4. Verify addLinkListener() has been registered
  5. Enable setLogging(true) to check SDK logs
  1. Confirm deferredDeeplinkEnabled = true (default)
  2. Verify installation was through Play Store (Install Referrer is not available for sideloaded installs)
  3. Install Referrer API may be limited on emulators

Version Compatibility

ItemRequired Version
compileSdk35
minSdk24
Kotlin2.0.0
AGP8.7.0
JDK17

For more details, refer to the official Android App Links documentation.


Migration from v0.0.x

API Mapping

Old API (v0.0.x)New API (v0.1.0+)
saveLimeLinkStatus(context, intent, key)LimeLinkSDK.init(app, config) — automatic
LimeLinkSDK.handleUniversalLink(ctx, intent, cb)LimeLinkSDK.init() + addLinkListener()
LimeLinkSDK.getSchemeFromIntent(intent)LimeLinkResult.originalUrl
LimeLinkSDK.parseQueryParams(intent)LimeLinkResult.queryParams
UrlHandler.parsePathParams(intent)LimeLinkResult.pathParams

Before (v0.0.x)

// No Application-level initialization

// Manual handling in each Activity
class MyActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        LimeLinkSDK.handleUniversalLink(this, intent) { uri -> /* ... */ }
        saveLimeLinkStatus(this, intent, "api_key")
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        LimeLinkSDK.handleUniversalLink(this, intent) { uri -> /* ... */ }
    }

    // Manual parsing
    val params = LimeLinkSDK.parseQueryParams(intent)
    val pathParams = UrlHandler.parsePathParams(intent)
}

After (v0.1.0)

// One-time initialization in Application
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        val config = LimeLinkConfig.Builder("api_key").build()
        LimeLinkSDK.init(this, config)
    }
}

// Register listener in Activity
class MyActivity : Activity() {
    private val listener = object : LimeLinkListener {
        override fun onDeeplinkReceived(result: LimeLinkResult) {
            // All info is included in result:
            // result.originalUrl, result.resolvedUri
            // result.queryParams, result.pathParams
            // result.isDeferred, result.referrerInfo
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        LimeLinkSDK.addLinkListener(listener)
    }

    override fun onDestroy() {
        super.onDestroy()
        LimeLinkSDK.removeLinkListener(listener)
    }
}

Deprecated methods will be removed in v1.0.0. Migrate to the Listener pattern as soon as possible.