Using the Android SDK

This guide will help you implement Linkrunner functionality in your native Android application.

Initialization

Initialize the Linkrunner SDK in your application, typically in your Application class or main activity: You can find your project token here. Note: This method returns a void. To get attribution data and deeplink information, use the getAttributionData method.
import android.app.Application
import io.linkrunner.sdk.LinkRunner
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

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

        // Initialize LinkRunner with SDK signing
        CoroutineScope(Dispatchers.IO).launch {
            try {
                LinkRunner.getInstance().init(
                    context = applicationContext,
                    token = "YOUR_PROJECT_TOKEN",
                    keyId = "YOUR_KEY_ID" // Required for SDK signing
                    secretKey = "YOUR_SECRET_KEY", // Required for SDK signing
                )

                println("LinkRunner initialized successfully")
            } catch (e: Exception) {
                println("Exception during initialization: ${e.message}")
            }
        }
    }
}

SDK Signing Parameters (Optional)

For enhanced security, the LinkRunner SDK requires the following signing parameters during initialization:
  • secretKey: A unique secret key used for request signing and authentication
  • keyId: A unique identifier for the key pair used in the signing process
You can find your project token, secret key, and key ID here.

User Registration

Call the signup method once after the user has completed your app’s onboarding process: It is strongly recommended to use the integrated platform’s identify function to set a persistent user_id once it becomes available (typically after signup or login). If the platform’s identifier function is not called, you must provide a user identifier for Mixpanel, PostHog, and Amplitude integration.
  • mixpanelDistinctId for Mixpanel
  • posthogDistinctId for PostHog
  • amplitudeDeviceId for Amplitude
private fun onSignup() {
    CoroutineScope(Dispatchers.IO).launch {
        try {
            val userData = UserDataRequest(
                id = "123", // Required: User ID
                name = "John Doe", // Optional
                phone = "9876543210", // Optional
                email = "user@example.com", // Optional
                mixpanelDistinctId = "mixpanel_distinct_id", // Optional - Mixpanel Distinct ID
                amplitudeDeviceId = "amplitude_device_id", // Optional - Amplitude User ID
                posthogDistinctId = "posthog_distinct_id" // Optional - PostHog Distinct ID
                userCreatedAt = "2024-01-01T00:00:00Z", // Optional
                isFirstTimeUser = true, // Optional
            )

            LinkRunner.getInstance().signup(
                userData = userData,
                additionalData = mapOf("custom_field" to "custom_value") // Optional: Any additional data
            )
            println("Signup successful")
        } catch (e: Exception) {
            println("Exception during signup: ${e.message}")
        }
    }
}

Getting Attribution Data

To get attribution data and deeplink information for the current installation, use the getAttributionData function:
val attributionDataResult = LinkRunner.getInstance().getAttributionData()
attributionDataResult.onSuccess { attributionData ->
    println("Attribution data: $attributionData")
    // Attribution data includes:
    // - deeplink: The deep link URL that led to app installation
    // - campaignData: Campaign information
}
data class AttributionData(
    val deeplink: String?,
    val campaignData: CampaignData,
    val attributionSource: String
)

data class CampaignData(
    val id: String,
    val name: String,
    val adNetwork: String?,
    val type: String,
    val installedAt: String,
    val storeClickAt: String?,
    val groupName: String,
    val assetName: String,
    val assetGroupName: String
)

Setting User Data

Call setUserData each time the app opens and the user is logged in:
private fun setUserData() {
    CoroutineScope(Dispatchers.IO).launch {
        try {
            val userData = UserDataRequest(
                id = "123", // Required: User ID
                name = "John Doe", // Optional
                phone = "9876543210", // Optional
                email = "user@example.com", // Optional
                mixpanelDistinctId = "mixpanel_distinct_id", // Optional - Mixpanel Distinct ID
                amplitudeDeviceId = "amplitude_device_id", // Optional - Amplitude User ID
                posthogDistinctId = "posthog_distinct_id" // Optional - PostHog Distinct ID
            )

            val result = LinkRunner.getInstance().setUserData(userData)

            result.onSuccess {
                println("User data set successfully")
            }.onFailure { error ->
                println("Error setting user data: ${error.message}")
            }
        } catch (e: Exception) {
            println("Exception setting user data: ${e.message}")
        }
    }
}

Setting CleverTap ID

Use the setAdditionalData method to set CleverTap ID:
private fun setIntegrationData() {
    CoroutineScope(Dispatchers.IO).launch {
        try {
            val integrationData: IntegrationData = IntegrationData(clevertapId="YOUR_CLEVERTAP_ID")

            val result = LinkRunner.getInstance().setAdditionalData(integrationData)

            result.onSuccess {
                println("CleverTap ID set successfully")
            }.onFailure { error ->
                println("Error setting CleverTap ID: ${error.message}")
            }
        } catch (e: Exception) {
            println("Exception setting CleverTap ID: ${e.message}")
        }
    }
}

Tracking Custom Events

Track custom events in your app:
private fun trackEvent() {
    CoroutineScope(Dispatchers.IO).launch {
        try {
            val result = LinkRunner.getInstance().trackEvent(
                eventName = "purchase_initiated", // Event name
                eventData = mapOf( // Optional: Event data
                    "product_id" to "12345",
                    "category" to "electronics",
                    "amount" to 99.99 // Include amount as a number for revenue sharing with ad networks like Google and Meta
                )
            )

            result.onSuccess {
                println("Event tracked successfully")
            }.onFailure { error ->
                println("Error tracking event: ${error.message}")
            }
        } catch (e: Exception) {
            println("Exception tracking event: ${e.message}")
        }
    }
}

Revenue Sharing with Ad Networks

To enable revenue sharing with ad networks like Google Ads and Meta, include an amount parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions:
private fun trackPurchaseEvent() {
    CoroutineScope(Dispatchers.IO).launch {
        try {
            val result = LinkRunner.getInstance().trackEvent(
                eventName = "purchase_completed",
                eventData = mapOf(
                    "product_id" to "12345",
                    "category" to "electronics",
                    "amount" to 149.99 // Revenue amount as a number
                )
            )

            result.onSuccess {
                println("Purchase event with revenue tracked successfully")
            }.onFailure { error ->
                println("Error tracking purchase event: ${error.message}")
            }
        } catch (e: Exception) {
            println("Exception tracking purchase event: ${e.message}")
        }
    }
}
For revenue sharing with ad networks to work properly, ensure the amount parameter is passed as a number (Double or Int), not as a string.

Revenue Tracking

Capturing Payments

Track payment information with the following details:
private fun capturePayment() {
    CoroutineScope(Dispatchers.IO).launch {
        try {
            val paymentData = CapturePaymentRequest(
                paymentId = "payment_123",  // Optional: Unique payment identifier
                userId = "user123",         // Required: User identifier
                amount = 99.99,              // Required: Payment amount
                type = PaymentType.FIRST_PAYMENT,  // Optional: Defaults to DEFAULT
                status = PaymentStatus.PAYMENT_COMPLETED  // Optional: Defaults to PAYMENT_COMPLETED
            )

            val result = LinkRunner.getInstance().capturePayment(paymentData)

            result.onSuccess {
                println("Payment captured successfully")
            }.onFailure { error ->
                println("Error capturing payment: ${error.message}")
            }
        } catch (e: Exception) {
            println("Exception capturing payment: ${e.message}")
        }
    }
}

Available Payment Types

TypeDescription
FIRST_PAYMENTUser’s first payment
WALLET_TOPUPAdding funds to wallet
FUNDS_WITHDRAWALWithdrawing funds
SUBSCRIPTION_CREATEDNew subscription created
SUBSCRIPTION_RENEWEDSubscription renewal
ONE_TIMEOne-time payment
RECURRINGRecurring payment
DEFAULTDefault payment type

Available Payment Statuses

StatusDescription
PAYMENT_INITIATEDPayment process started
PAYMENT_COMPLETEDPayment successfully completed
PAYMENT_FAILEDPayment failed
PAYMENT_CANCELLEDPayment was cancelled

Removing Payments

To remove or refund a payment:
private fun removePayment() {
    CoroutineScope(Dispatchers.IO).launch {
        try {
            // Either paymentId or userId must be provided
            val removeRequest = RemovePaymentRequest(
                paymentId = "payment_123",  // Optional: Payment ID to remove
                userId = "user123"           // Optional: User ID to remove payments for
            )

            val result = LinkRunner.getInstance().removePayment(removeRequest)

            result.onSuccess {
                println("Payment removed successfully")
            }.onFailure { error ->
                println("Error removing payment: ${error.message}")
            }
        } catch (e: Exception) {
            println("Exception removing payment: ${e.message}")
        }
    }
}

Enhanced Privacy Controls

The SDK offers options to enhance user privacy:
// Enable PII (Personally Identifiable Information) hashing
LinkRunner.getInstance().enablePIIHashing(true)

// Check if PII hashing is enabled
val isHashingEnabled = LinkRunner.getInstance().isPIIHashingEnabled()
When PII hashing is enabled, sensitive user data like name, email, and phone number are hashed using SHA-256 before being sent to Linkrunner servers.

Function Placement Guide

FunctionWhere to PlaceWhen to Call
LinkRunner.getInstance().initApplication classOnce when app starts
LinkRunner.getInstance().getAttributionDataAttribution data handling flowWhenever the attribution data is needed
LinkRunner.getInstance().setAdditionalDataIntegration codeWhen third-party integration IDs are available
LinkRunner.getInstance().signupOnboarding flowOnce after user completes onboarding
LinkRunner.getInstance().setUserDataAuthentication logicEvery time app opens with logged-in user
LinkRunner.getInstance().trackEventThroughout appWhen specific user actions occur
LinkRunner.getInstance().capturePaymentPayment processingWhen user makes a payment
LinkRunner.getInstance().removePaymentRefund flowWhen payment needs to be removed

Complete Example

Here’s a simplified example showing how to integrate Linkrunner in a native Android app: You can find your project token here.
import android.app.Application
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import io.linkrunner.sdk.LinkRunner
import io.linkrunner.sdk.models.request.UserDataRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

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

        // Initialize LinkRunner with SDK signing
        CoroutineScope(Dispatchers.IO).launch {
            try {
                LinkRunner.getInstance().init(
                    context = applicationContext,
                    token = "YOUR_PROJECT_TOKEN",
                    secretKey = "YOUR_SECRET_KEY", // Required for SDK signing
                    keyId = "YOUR_KEY_ID" // Required for SDK signing
                )
            } catch (e: Exception) {
                println("Error initializing LinkRunner: ${e.message}")
            }
        }
    }
}

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Button to track an event
        findViewById<android.widget.Button>(R.id.trackEventButton).setOnClickListener {
            trackCustomEvent()
        }
    }

    override fun onResume() {
        super.onResume()
        // Trigger deeplink when navigation is ready
        triggerDeeplink()

        // Set user data if user is logged in
        if (isUserLoggedIn()) {
            setUserData()
        }
    }

    private fun isUserLoggedIn(): Boolean {
        // Your login check logic
        return true
    }

    private fun setUserData() {
        CoroutineScope(Dispatchers.IO).launch {
            try {
                val userData = UserDataRequest(
                    id = "123",
                    name = "John Doe",
                    email = "user@example.com"
                )

                LinkRunner.getInstance().setUserData(userData)
            } catch (e: Exception) {
                println("Error setting user data: ${e.message}")
            }
        }
    }

    private fun triggerDeeplink() {
        CoroutineScope(Dispatchers.IO).launch {
            try {
                LinkRunner.getInstance().triggerDeeplink()
            } catch (e: Exception) {
                println("Error triggering deeplink: ${e.message}")
            }
        }
    }

    private fun trackCustomEvent() {
        CoroutineScope(Dispatchers.IO).launch {
            try {
                LinkRunner.getInstance().trackEvent(
                    eventName = "button_clicked",
                    eventData = mapOf("screen" to "main")
                )
            } catch (e: Exception) {
                println("Error tracking event: ${e.message}")
            }
        }
    }
}

Support

If you encounter issues during integration, contact us at darshil@linkrunner.io.