LimeLink Android SDK
SDK Version: 0.1.0 | Min SDK: 24 | Compile SDK: 35
Getting Started
Requirements
| Item | Required Version |
|---|---|
| compileSdk | 35 |
| minSdk | 24 |
| Kotlin | 2.0.0 |
| AGP | 8.7.0 |
| JDK | 17 |
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
| Parameter | Type | Default | Description |
|---|---|---|---|
apiKey | String | (required) | LimeLink API key |
baseUrl | String | https://limelink.org/ | API server URL |
loggingEnabled | Boolean | false | Enable SDK internal debug logs |
deferredDeeplinkEnabled | Boolean | true | Auto-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 viaonNewIntentwhen 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>
Receiving Deep Links (Listener Pattern)
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
| Field | Type | Description |
|---|---|---|
originalUrl | String? | Original URL |
resolvedUri | Uri? | Final redirect URI resolved by the API |
queryParams | Map<String, String> | URL query parameters |
pathParams | PathParamResponse | Path parameters (mainPath, subPath) |
isDeferred | Boolean | Whether this is a deferred deeplink (default false) |
referrerInfo | ReferrerInfo? | Install Referrer info (available when deferred) |
LimeLinkError Fields
| Field | Type | Description |
|---|---|---|
code | Int | Error code |
message | String | Error message |
exception | Exception? | Underlying exception (if any) |
Distinguishing Universal Link vs Deferred Deeplink
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
}
}
Universal Link Flow
Subdomain Pattern (Primary)
https://{subdomain}.limelink.org/link/{linkSuffix}
https://{subdomain}.limelink.org/link/{linkSuffix}?key1=val1&key2=val2
Flow:
- User clicks a URL like
https://abc.limelink.org/link/promo?campaign=summer - SDK extracts the subdomain (
abc) and link suffix (promo) from the URL - SDK extracts the full original URL (including query parameters) as
full_request_url - SDK fetches headers from
https://abc.limelink.orgfor additional context - SDK calls
GET /api/v1/app/dynamic_link/promo?full_request_url={encoded_url}with collected headers - API returns a
urivalue, which the SDK delivers to the registered listener - 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.
Legacy Deeplink Pattern
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
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
- Redirecting users to specific content after app installation
- Tracking user acquisition sources from marketing campaigns
- Providing personalized onboarding experiences based on the original link
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}")
}
}
Manual Deferred Deep Link (Pass Suffix Directly)
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
| Field | Type | Description |
|---|---|---|
referrerUrl | String? | Raw referrer string from Play Store |
clickTimestamp | Long | Ad click time (in seconds) |
installTimestamp | Long | App install start time (in seconds) |
limeLinkUrl | String? | LimeLink URL extracted from referrer |
limeLinkDetail | LimeLinkUrl? | 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
| Field | Type | Description |
|---|---|---|
referrer | String | Full raw referrer string |
url | String | URL without query string |
fullUrl | String | Full URL including query string |
queryString | String? | Query string (part after ?, null if none) |
queryParams | Map<String, String> | Query parameter map |
API Reference
Recommended API (v0.1.0+)
| Method | Description |
|---|---|
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
| Class | Description |
|---|---|
LimeLinkConfig | SDK configuration (Builder pattern) |
LimeLinkResult | Deep link processing result |
LimeLinkError | Error information (code, message, exception?) |
ReferrerInfo | Install Referrer information |
LimeLinkUrl | Referrer URL detailed structure (v0.1.0+) |
PathParamResponse | Path 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
Deep link not received
- Verify
intent-filteris configured inAndroidManifest.xml - Confirm
android:launchMode="singleTop"is set - Ensure
LimeLinkSDK.init()has been called - Verify
addLinkListener()has been registered - Enable
setLogging(true)to check SDK logs
Deferred deep link not working
- Confirm
deferredDeeplinkEnabled = true(default) - Verify installation was through Play Store (Install Referrer is not available for sideloaded installs)
- Install Referrer API may be limited on emulators
Version Compatibility
| Item | Required Version |
|---|---|
| compileSdk | 35 |
| minSdk | 24 |
| Kotlin | 2.0.0 |
| AGP | 8.7.0 |
| JDK | 17 |
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.