Mobile SDKDeep Link
Android
AdStage DeepLink Integration Guide (Android)
Table of Contents
- Overview
- Project Setup
- AndroidManifest.xml Configuration
- Application Class Setup
- MainActivity Setup
- Handling Deep Link Reception
- Creating Deep Links
- Advanced Features
- Troubleshooting
Overview
The AdStage DeepLink SDK provides the following features:
- Real-time Deep Links: Immediate processing via URL Scheme and App Links
- Deferred Deep Links: Restoration on first launch after app installation
- Dynamic Deep Link Creation: Trackable link generation through server API
- Attribution Tracking: Marketing analysis based on UTM parameters
Project Setup
Adding Gradle Dependencies
settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://repo.nbase.io/repository/nbase-releases") }
}
}build.gradle.kts (Module: app)
dependencies {
implementation("io.nbase:adapter-adstage:3.0.+")
// Required dependencies
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
}AndroidManifest.xml Configuration
1. Permission Setup
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Install Referrer permission (for deferred deep links) -->
<uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" />
<application>
<!-- ... -->
</application>
</manifest>2. DeepLink Intent Filter Setup
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask">
<!-- Default launcher -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- URL Scheme deep link (e.g., myapp://promo/summer) -->
<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="myapp"
android:host="promo" />
</intent-filter>
<!-- App Link (https://go.myapp.com/...) -->
<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="go.myapp.com" />
</intent-filter>
<!-- AdStage deep link domain (e.g., https://go.adstage.net/...) -->
<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="go.adstage.net" />
</intent-filter>
</activity>3. Important launchMode Settings
<!-- Recommended: singleTask -->
android:launchMode="singleTask"
<!-- When using singleTask:
- Reuses existing Activity if available
- onNewIntent() is called
- Brings to top of Task stack
-->Application Class Setup
MyApplication.kt
package com.example.myapp
import android.app.Application
import io.nbase.adapter.adstage.AdStage
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize AdStage SDK
AdStage.initialize(
context = this,
apiKey = "your-api-key-here",
serverUrl = "https://api.adstage.app" // Optional, default can be used
)
// Setup deep link listener
setupDeeplinkListener()
}
private fun setupDeeplinkListener() {
AdStage.setDeeplinkListener(object : io.nbase.adapter.adstage.models.DeeplinkListener {
override fun onDeeplinkReceived(data: io.nbase.adapter.adstage.models.DeeplinkData) {
android.util.Log.d("AdStage", """
✅ Deep link received
- Short Path: ${data.shortPath}
- Link ID: ${data.linkId}
- Source: ${data.source}
- Parameters: ${data.parameters}
""".trimIndent())
// Handle globally or pass through event bus
handleGlobalDeeplink(data)
}
override fun onDeeplinkFailed(error: String, shortPath: String?) {
android.util.Log.e("AdStage", """
❌ Deep link failed
- Error: $error
- Short Path: $shortPath
""".trimIndent())
}
})
}
private fun handleGlobalDeeplink(data: io.nbase.adapter.adstage.models.DeeplinkData) {
// Pass to current Activity via global event bus or SharedFlow
// Or store in deep link manager for Activity to retrieve
}
}Register Application in AndroidManifest.xml
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.MyApp">
<!-- ... -->
</application>MainActivity Setup
MainActivity.kt
package com.example.myapp
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import io.nbase.adapter.adstage.AdStage
import io.nbase.adapter.adstage.models.DeeplinkData
import io.nbase.adapter.adstage.models.DeeplinkListener
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Handle deep link (when app is first launched or launched from background)
handleIntent(intent)
// Setup local deep link listener (optional)
setupLocalDeeplinkListener()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
setIntent(intent) // Important: Replace with new Intent
Log.d(TAG, "onNewIntent called")
// Handle deep link (when new deep link received while app is running)
handleIntent(intent)
}
private fun handleIntent(intent: Intent?) {
if (intent == null) {
Log.w(TAG, "Intent is null")
return
}
Log.d(TAG, """
Intent received:
- Action: ${intent.action}
- Data: ${intent.data}
- Extras: ${intent.extras?.keySet()?.joinToString()}
""".trimIndent())
// Pass Intent to AdStage SDK
val handled = AdStage.handleIntent(this, intent)
if (handled) {
Log.i(TAG, "✅ AdStage handled the deep link")
} else {
Log.d(TAG, "ℹ️ Not an AdStage deep link")
// Handle regular Intent
handleRegularIntent(intent)
}
}
private fun handleRegularIntent(intent: Intent) {
when (intent.action) {
Intent.ACTION_VIEW -> {
// Handle regular web links or custom schemes
val uri = intent.data
Log.d(TAG, "Regular deep link: $uri")
}
// Handle other actions...
}
}
/**
* Local deep link listener (Activity-only handling)
* Optional if global listener is set in Application
*/
private fun setupLocalDeeplinkListener() {
AdStage.setDeeplinkListener(object : DeeplinkListener {
override fun onDeeplinkReceived(data: DeeplinkData) {
Log.d(TAG, "📱 Deep link received in Activity: ${data.shortPath}")
// Handle business logic
when {
data.parameters.containsKey("campaign") -> {
handleCampaignDeeplink(data)
}
data.parameters.containsKey("promo") -> {
handlePromoDeeplink(data)
}
else -> {
handleDefaultDeeplink(data)
}
}
}
override fun onDeeplinkFailed(error: String, shortPath: String?) {
Log.e(TAG, "❌ Deep link failed: $error")
// Show error UI
}
})
}
private fun handleCampaignDeeplink(data: DeeplinkData) {
val campaign = data.parameters["campaign"]
val channel = data.parameters["channel"]
Log.d(TAG, """
🎯 Campaign deep link handling
- Campaign: $campaign
- Channel: $channel
""".trimIndent())
// Navigate to campaign screen
// startActivity(Intent(this, CampaignActivity::class.java).apply {
// putExtra("campaign", campaign)
// })
}
private fun handlePromoDeeplink(data: DeeplinkData) {
val promoCode = data.parameters["promo"]
Log.d(TAG, "🎁 Promo code: $promoCode")
// Apply promotion
// applyPromoCode(promoCode)
}
private fun handleDefaultDeeplink(data: DeeplinkData) {
Log.d(TAG, "📋 Default deep link handling: ${data.shortPath}")
// Stay on main screen or navigate to specific screen
}
override fun onDestroy() {
super.onDestroy()
// Clean up listener (prevent memory leaks)
// Don't clear here if using global listener
// AdStage.clearDeeplinkListener()
}
}Handling Deep Link Reception
1. Real-time Deep Links (App Running)
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AdStage.setDeeplinkListener(object : DeeplinkListener {
override fun onDeeplinkReceived(data: DeeplinkData) {
// data.shortPath: "SDGWNBB"
// data.linkId: "507f1f77bcf86cd799439011"
// data.source: REALTIME or DEFERRED
// data.parameters: Map<String, String>
// Extract UTM parameters
val utmSource = data.parameters["utmSource"]
val utmMedium = data.parameters["utmMedium"]
val utmCampaign = data.parameters["utmCampaign"]
// Extract custom parameters
val customParam = data.parameters["customKey"]
// Navigate to screen
navigateToScreen(data)
}
override fun onDeeplinkFailed(error: String, shortPath: String?) {
// Handle error
showErrorDialog(error)
}
})
}
}2. Deferred Deep Links (First Launch After Install)
// Automatically handled!
// Install Referrer is automatically queried at AdStage.initialize()
// If saved deep link exists, onDeeplinkReceived is automatically called
// For manual handling if needed:
AdStage.handleInstallReferrer(context)3. Distinguishing Deep Link Source
override fun onDeeplinkReceived(data: DeeplinkData) {
when (data.source) {
DeepLinkSource.REALTIME -> {
Log.d(TAG, "🔗 Real-time deep link (received while app running)")
// Can navigate immediately
}
DeepLinkSource.INSTALL -> {
Log.d(TAG, "📦 Deferred deep link (first launch after install)")
// Navigate after onboarding, etc.
}
else -> {
Log.d(TAG, "❓ Unknown source")
}
}
}Creating Deep Links
1. Request Object Method
import io.nbase.adapter.adstage.AdStage
import io.nbase.adapter.adstage.models.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class DeeplinkManager {
fun createSimpleDeeplink() {
CoroutineScope(Dispatchers.Main).launch {
try {
val request = CreateDeeplinkRequest(
name = "Summer Promotion Link",
description = "2024 Summer Season Promotion",
channel = "google-ads",
campaign = "summer2024",
parameters = mapOf(
"promo" to "SUMMER20",
"discount" to 20
)
)
val response = AdStage.createDeeplink(request)
Log.d(TAG, """
✅ Deep link created
- Short URL: ${response.shortUrl}
- Short Path: ${response.shortPath}
- ID: ${response.id}
""".trimIndent())
// Share generated URL
shareUrl(response.shortUrl)
} catch (e: Exception) {
Log.e(TAG, "❌ Deep link creation failed: ${e.message}")
}
}
}
}2. Builder Pattern (DSL Style)
fun createDeeplinkWithBuilder() {
CoroutineScope(Dispatchers.Main).launch {
try {
val response = AdStage.createDeeplink("Winter Promotion") {
description("2024-2025 Winter Season Promotion")
shortPath("WINTER24") // Custom path
// Tracking parameters
channel("facebook-ads")
subChannel("instagram")
campaign("winter2024")
adGroup("fashion-lovers")
creative("banner-001")
content("hero-image")
keyword("winter-sale")
// Redirect configuration
redirectConfig {
type(RedirectType.APP) // STORE, APP, WEB
// Android settings
android {
appScheme("myapp://promo/winter")
packageName("com.example.myapp")
webUrl("https://example.com/promo/winter")
}
// iOS settings
ios {
appScheme("myapp://promo/winter")
bundleId("com.example.myapp")
appStoreId("123456789")
webUrl("https://example.com/promo/winter")
}
// Desktop settings
desktop {
webUrl("https://example.com/promo/winter")
}
}
// Custom parameters
parameter("discount", 30)
parameter("promoCode", "WINTER30")
parameter("validUntil", "2025-03-31")
// Status setting
status(DeeplinkStatus.ACTIVE)
}
Log.d(TAG, "✅ Short URL: ${response.shortUrl}")
} catch (e: Exception) {
Log.e(TAG, "❌ Error: ${e.message}")
}
}
}3. Callback Method (Async)
fun createDeeplinkWithCallback() {
val request = CreateDeeplinkRequest(
name = "App Invite Link",
channel = "referral",
campaign = "invite-friend",
parameters = mapOf(
"referrer" to "USER123",
"bonus" to 5000
)
)
// Callback method uses suspend function wrapped in coroutine
CoroutineScope(Dispatchers.Main).launch {
try {
val response = AdStage.createDeeplink(request)
onSuccess(response)
} catch (e: Exception) {
onError(e)
}
}
}
private fun onSuccess(response: CreateDeeplinkResponse) {
Log.d(TAG, "Deep link created successfully: ${response.shortUrl}")
// Update UI
runOnUiThread {
textView.text = response.shortUrl
shareButton.isEnabled = true
}
}
private fun onError(error: Exception) {
Log.e(TAG, "Deep link creation failed: ${error.message}")
runOnUiThread {
Toast.makeText(this, "Deep link creation failed", Toast.LENGTH_SHORT).show()
}
}4. RedirectType Explanation
enum class RedirectType {
STORE, // If app not installed → Go to store
// If app installed → Launch app (deferred deep link)
APP, // If app not installed → Go to web fallback URL
// If app installed → Launch app (real-time deep link)
WEB // Always go to web URL
// Regardless of app installation
}5. Deep Link Sharing Example
fun shareDeeplink(shortUrl: String) {
val shareIntent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, """
🎁 Special Promotion Invite!
Sign up through this link and get a $50 discount coupon.
$shortUrl
""".trimIndent())
}
startActivity(Intent.createChooser(shareIntent, "Invite Friends"))
}Advanced Features
1. Global User Attributes Setup
// Set in Application onCreate()
val userAttributes = UserAttributes(
gender = "male",
country = "KR",
city = "Seoul",
age = "28",
language = "ko-KR"
)
AdStage.setUserAttributes(userAttributes)
// Automatically included in subsequent trackEvent calls2. Global Device Info Setup
val deviceInfo = DeviceInfo(
category = "mobile",
platform = "Android",
model = Build.MODEL,
appVersion = BuildConfig.VERSION_NAME,
osVersion = Build.VERSION.RELEASE
)
AdStage.setDeviceInfo(deviceInfo)3. User ID and Session Management
// On login
AdStage.setUserId("user_123456")
// On logout
AdStage.setUserId(null)
// Start new session
AdStage.startNewSession()
// Get current session ID
val sessionId = AdStage.getSessionId()
Log.d(TAG, "Current Session: $sessionId")4. Promotion Banner Integration
// Get promotion list
CoroutineScope(Dispatchers.Main).launch {
val params = PromotionListParams.builder()
.bannerType("NATIVE")
.region("KR")
.limit(10)
.build()
val response = AdStage.getPromotionList(params)
response.promotions.forEach { promotion ->
Log.d(TAG, """
Promotion: ${promotion.appName}
Banner: ${promotion.bannerUrl}
""".trimIndent())
}
}
// Handle promotion click
CoroutineScope(Dispatchers.Main).launch {
val result = AdStage.handlePromotionClick(context, promotion)
when (result) {
is PromotionClickResult.Success -> {
Log.d(TAG, "Store opened: ${result.storeUrl}")
}
is PromotionClickResult.Failure -> {
Log.e(TAG, "Failed: ${result.error}")
}
}
}Troubleshooting
1. Deep Links Not Being Received
Checklist:
- intent-filter correctly configured in AndroidManifest.xml
- Verify
android:exported="true"setting - Recommend
android:launchMode="singleTask"setting - Verify AdStage.initialize() is called
- Verify setDeeplinkListener() is called
- Verify handleIntent() is called
Debugging:
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Log.d(TAG, """
Intent Debug:
- Action: ${intent?.action}
- Data: ${intent?.data}
- Scheme: ${intent?.data?.scheme}
- Host: ${intent?.data?.host}
- Path: ${intent?.data?.path}
""".trimIndent())
handleIntent(intent)
}2. Deferred Deep Links Not Working
Causes:
- Missing Install Referrer permission
- Installation not through Google Play Store (direct APK install)
- Install Referrer API initialization failed
Solution:
// Manually handle Install Referrer
AdStage.handleInstallReferrer(applicationContext)
// Check logs
Log.d(TAG, "Install Referrer processed")3. App Link Verification Failed
Verification Method:
# 1. Check Digital Asset Links file
https://go.myapp.com/.well-known/assetlinks.json
# 2. Use verification tool
https://developers.google.com/digital-asset-links/tools/generator
# 3. Test with ADB
adb shell am start -a android.intent.action.VIEW -d "https://go.myapp.com/ABCDEF"assetlinks.json Example:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": [
"14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
]
}
}]4. ProGuard/R8 Obfuscation Issues
proguard-rules.pro:
# AdStage SDK
-keep class io.nbase.adapter.adstage.** { *; }
-keepclassmembers class io.nbase.adapter.adstage.** { *; }
# Model classes
-keep class io.nbase.adapter.adstage.models.** { *; }
# OkHttp
-dontwarn okhttp3.**
-keep class okhttp3.** { *; }
# Kotlin Coroutines
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
5. Multi-Process Environment
<!-- If you have Activity running in separate process -->
<activity
android:name=".SomeActivity"
android:process=":separate">
<!-- Need separate initialization to handle deep links in this Activity -->
</activity>// Need to call AdStage.initialize() in each process
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize in all processes
AdStage.initialize(this, apiKey)
}
}Testing Methods
1. ADB Testing
# URL Scheme test
adb shell am start -a android.intent.action.VIEW -d "myapp://promo/summer"
# App Link test
adb shell am start -a android.intent.action.VIEW -d "https://go.myapp.com/ABCDEF"
# With parameters
adb shell am start -a android.intent.action.VIEW -d "myapp://promo?campaign=summer&discount=20"2. Intent Log Verification
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let {
Log.d(TAG, "=== Intent Debug ===")
Log.d(TAG, "Action: ${it.action}")
Log.d(TAG, "Data: ${it.data}")
Log.d(TAG, "Extras: ${it.extras?.keySet()?.joinToString()}")
it.data?.let { uri ->
Log.d(TAG, "URI Scheme: ${uri.scheme}")
Log.d(TAG, "URI Host: ${uri.host}")
Log.d(TAG, "URI Path: ${uri.path}")
Log.d(TAG, "URI Query: ${uri.query}")
}
}
}3. Real Device Testing
// Create test deep link
CoroutineScope(Dispatchers.Main).launch {
val response = AdStage.createDeeplink("Test Link") {
channel("test")
campaign("test-campaign")
parameter("test", "true")
}
Log.d(TAG, "Test URL: ${response.shortUrl}")
// Copy URL and test in browser or messenger
}References
- AdStage Official Documentation
- Android App Links Guide
- Install Referrer API
- Deep Links Best Practices

