Android SDK
Complete reference for the AppRefer Android SDK. Native Kotlin library built on HttpURLConnection and the Google Play Install Referrer API. No OkHttp, no Retrofit, no heavy transitive dependencies.
Installation#
The SDK is distributed via JitPack. Add the JitPack repository (once per project) and the dependency:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven("https://jitpack.io")
}
}dependencies {
implementation("com.github.AppAgentic:apprefer-android-sdk:0.4.1")
}Maven Central publish pending
com.apprefer:apprefer-android-sdk once the Sonatype namespace is claimed. Track progress at github.com/AppAgentic/apprefer-android-sdk.Permissions#
The library declares only INTERNET — nothing else is required in your AndroidManifest.xml. The SDK does not declare com.google.android.gms.permission.AD_ID so you do not have to make a Play Data Safety disclosure on its behalf.
Install Referrer#
Google Play Install Referrer is bundled transitively — you do not need to add com.android.installreferrer:installreferrer yourself. After a user clicks a trk.apprefer.com/api/c/<linkId> link and installs from Google Play, the referrer string is delivered automatically — the SDK parses gclid, fbclid, ar_click_id, and the utm_* parameters. No developer setup needed.
Google Advertising ID (optional)#
If play-services-ads-identifier is on your app's classpath, the SDK will read the GAID at configure() to improve deterministic matching. If you exclude it (for Play Data Safety simplicity), the SDK degrades gracefully — Install Referrer + IP/UA fingerprint still deliver attribution.
Quickstart#
Call AppRefer.configure() as early as possible in your app — typically in your first Activity.onCreate() or in an Application.onCreate() coroutine scope. The function is suspend; use lifecycleScope or any other CoroutineScope you control.
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.apprefer.sdk.AppRefer
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
val attribution = AppRefer.configure(
context = this@MainActivity,
apiKey = "pk_live_your_key_here",
debug = BuildConfig.DEBUG,
)
if (attribution != null) {
Log.i("AppRefer", "Network: ${attribution.network}")
Log.i("AppRefer", "Match type: ${attribution.matchType}")
}
}
}
}Java callback variant#
Not every Android host is Kotlin. Every suspend function has a @JvmOverloads callback overload so Java code can call it without any coroutine setup:
import com.apprefer.sdk.AppRefer;
import com.apprefer.sdk.AppReferCallback;
import com.apprefer.sdk.models.Attribution;
AppRefer.configure(
this,
"pk_live_your_key_here",
/* userId */ null,
/* debug */ false,
/* logLevel */ 1,
new AppReferCallback<Attribution>() {
@Override public void onResult(Attribution attribution) {
// handle attribution (nullable)
}
@Override public void onError(Throwable error) {
// Won't normally fire — the SDK swallows failures by design.
}
}
);API Reference#
The SDK never crashes the host app
runCatching / a safe-run helper. If called before configure(), methods return silently with a debug log. No throws, no crashes.AppRefer.configure(context, apiKey, userId?, debug?, logLevel?)#
Initialize the SDK and resolve attribution. Call once at app launch. Calling configure() multiple times reuses the existing singleton state — only the first call performs a network request.
| Parameter | Type | Default | Description |
|---|---|---|---|
context | Context | required | Any Context — SDK stores applicationContext internally |
apiKey | String | required | Your SDK key (pk_live_... or pk_test_...) |
userId | String? | null | Optional user ID to associate with attribution |
debug | Boolean | false | Enable verbose Logcat output |
logLevel | Int | 1 | 0 = none, 1 = info, 2 = debug — advanced tuning |
| Details | |
|---|---|
| Signature | suspend fun configure(context: Context, apiKey: String, userId: String? = null, debug: Boolean = false, logLevel: Int = 1): Attribution? |
| Returns | Attribution? — the resolved attribution, or null for organic installs |
| Throws | Never |
AppRefer.trackEvent(eventName, properties?, revenue?, currency?)#
Send a custom event to AppRefer. Purchases are handled automatically via RevenueCat webhooks — use trackEvent for signup, tutorial completion, lead events, etc.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventName | String | required | Event name (e.g., "signup", "tutorial_complete") |
properties | Map<String, Any?>? | null | Arbitrary key-value metadata |
revenue | Double? | null | Revenue amount (e.g., 9.99) |
currency | String? | null | ISO 4217 currency code (e.g., "USD") |
lifecycleScope.launch {
AppRefer.trackEvent(
eventName = "tutorial_complete",
properties = mapOf("level" to 3, "time_spent" to 42),
)
// With revenue
AppRefer.trackEvent(
eventName = "purchase",
revenue = 9.99,
currency = "USD",
)
}AppRefer.setAdvancedMatching(email?, phone?, firstName?, lastName?, dateOfBirth?)#
Send hashed PII to improve ad network match rates. All values are SHA256-hashed on-device before being sent to the server. The server never receives plaintext PII.
| Parameter | Type | Default | Description |
|---|---|---|---|
email | String? | null | User's email address |
phone | String? | null | Phone number in E.164 format |
firstName | String? | null | User's first name |
lastName | String? | null | User's last name |
dateOfBirth | String? | null | Date of birth (YYYYMMDD) |
lifecycleScope.launch {
AppRefer.setAdvancedMatching(
email = "user@example.com",
phone = "+15551234567",
firstName = "Jane",
lastName = "Doe",
)
}AppRefer.setUserId(userId)#
Associate a user ID with the device's attribution. Typically called with the RevenueCat app user ID.
appreferId must be set on RevenueCat FIRST
appreferId subscriber attribute on RevenueCat. This is how AppRefer links RevenueCat webhook events back to the attributed device. Without it, purchase events from RevenueCat cannot be matched to attributions and will not be forwarded to ad networks.lifecycleScope.launch {
val attribution = AppRefer.configure(context, apiKey = "pk_live_...")
// REQUIRED: link AppRefer device ID to RevenueCat
AppRefer.getDeviceId()?.let { deviceId ->
Purchases.sharedInstance.setAttributes(mapOf("appreferId" to deviceId))
}
// Optional: mirror RevenueCat user ID back to AppRefer
AppRefer.setUserId(Purchases.sharedInstance.appUserID)
}AppRefer.getAttribution()#
Returns the cached attribution from the last configure() call. Synchronous — no coroutine context required. Returns null if configure() has not completed or the device is organic.
| Details | |
|---|---|
| Signature | @JvmStatic fun getAttribution(): Attribution? |
| Returns | Attribution? — cached attribution result |
AppRefer.getDeviceId()#
Returns the device ID generated by AppRefer. This is a stable UUID stored in the app's SharedPreferences, persisted across launches.
| Details | |
|---|---|
| Signature | @JvmStatic fun getDeviceId(): String? |
| Returns | String? — the device UUID, or null before configure() |
Kill switch
configure() response turns the SDK off — all subsequent trackEvent calls become no-ops until you re-enable it. No code change required in the host app.Attribution Model#
The Attribution data class returned by configure() contains the following properties:
| Property | Type | Description |
|---|---|---|
network | String? | Ad network name (e.g., "meta", "google", "tiktok") |
matchType | String? | Confidence level of the attribution match |
campaignName | String? | Campaign name from the tracking link |
adsetName | String? | Ad set / ad group name |
adName | String? | Ad creative name |
fbclid | String? | Facebook click ID |
gclid | String? | Google click ID |
ttclid | String? | TikTok click ID |
clickId | String? | Generic click ID (AppRefer internal) |
deviceId | String? | Device UUID assigned by AppRefer |
createdAt | String? | ISO 8601 timestamp of the attribution |
Full Integration Example#
This example shows a complete integration with RevenueCat, including attribution resolution, user ID linking, and advanced matching after signup:
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.apprefer.sdk.AppRefer
import com.revenuecat.purchases.Purchases
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Configure RevenueCat
Purchases.configure(PurchasesConfiguration.Builder(this, "rc_public_key").build())
lifecycleScope.launch {
// 1. Initialize AppRefer and resolve attribution
val attribution = AppRefer.configure(
context = this@MainActivity,
apiKey = "pk_live_your_key_here",
)
Log.i("AppRefer", "Network: ${attribution?.network ?: "organic"}")
// 2. CRITICAL: link AppRefer device ID to RevenueCat
// Without this, purchases CANNOT be attributed to ad campaigns.
AppRefer.getDeviceId()?.let { deviceId ->
Purchases.sharedInstance.setAttributes(
mapOf("appreferId" to deviceId)
)
}
// 3. Mirror the RevenueCat user ID back to AppRefer
AppRefer.setUserId(Purchases.sharedInstance.appUserID)
}
}
// Later, after the user signs up...
fun onUserSignup(email: String, firstName: String) {
lifecycleScope.launch {
// 4. Advanced matching (hashed on-device)
AppRefer.setAdvancedMatching(email = email, firstName = firstName)
// 5. Track the signup event
AppRefer.trackEvent("signup")
}
}
}Best Practices#
- Call
configure()as early as possible in the app lifecycle (first Activity'sonCreate()or theApplicationclass). The sooner you call it, the more accurate the attribution window. - Always set
appreferIdon RevenueCat immediately afterconfigure(). This is the primary mechanism for matching RevenueCat purchase webhooks back to AppRefer attributions. Without it, purchases cannot be attributed. - Run
configure()insidelifecycleScope.launchor any coroutine scope you control. Do not call it onDispatchers.Main— the SDK already hops toDispatchers.IOfor network work. - Optionally call
setUserId()with the RevenueCat user ID for additional matching. This supplementsappreferIdbut is not a replacement for it. - Use
pk_test_keys during development. Sandbox events are completely isolated from production and never forwarded to ad networks. - Enable
debug = truein development to see verbose Logcat output. Disable in release builds.
Requirements#
| Requirement | Minimum Version |
|---|---|
| Android | API 21 (Android 5.0 Lollipop) |
| Kotlin | 1.9+ |
| JDK toolchain | 17 |
| AGP | 8.0+ |
| Dependencies | kotlinx.coroutines, com.android.installreferrer (bundled). No OkHttp, no Retrofit, no Moshi. |
Source code