From b821ab2c176354a429d52998eabb3293abbd632f Mon Sep 17 00:00:00 2001 From: James O'Claire Date: Sun, 23 Feb 2025 17:30:47 +0800 Subject: [PATCH 1/6] WIP: Added trackEvent --- .../java/dev/openattribution/sdk/OAWorker.kt | 203 ++++++++++-------- .../java/dev/openattribution/sdk/Track.kt | 13 +- .../dev/openattribution/sdk/TrackingEvent.kt | 12 ++ 3 files changed, 130 insertions(+), 98 deletions(-) create mode 100644 OpenAttribution/src/main/java/dev/openattribution/sdk/TrackingEvent.kt diff --git a/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt b/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt index 5fa69ea..8b87c91 100644 --- a/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt +++ b/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt @@ -4,6 +4,7 @@ import android.content.Context import android.provider.Settings import android.util.Log import androidx.work.CoroutineWorker +import androidx.work.Data import androidx.work.WorkerParameters import kotlinx.coroutines.Dispatchers import com.google.android.gms.ads.identifier.AdvertisingIdClient @@ -17,16 +18,8 @@ import java.util.UUID import java.util.concurrent.TimeUnit -data class TrackingEvent( - val oaUid: String, - val ifa: String?, - val androidId: String, - val eventId: String, - val eventUid: String, - val eventTime: Long, -) -class TrackAppOpenWorker( +class TrackEventWorker( private val appContext: Context, workerParams: WorkerParameters ) : CoroutineWorker(appContext, workerParams) { @@ -34,102 +27,122 @@ class TrackAppOpenWorker( override suspend fun doWork(): Result { Log.d("MyOA", "doWork start") - val userIdManager = UserIdManager.getInstance(appContext) - val baseUrl = OpenAttribution.getBaseUrl() - - try { - val androidId = Settings.Secure.getString(appContext.contentResolver, Settings.Secure.ANDROID_ID) - val myTimestamp = System.currentTimeMillis() - val eventId = "app_open" - val myUid = UUID.randomUUID().toString() - val myOaUid = userIdManager.getUserId() - val basePackageName = appContext.packageName - val appendedPackageName = if (EmulatorDetector.isEmulator()) { - "${basePackageName}_test" - } else { - basePackageName - } - - val gaid = withContext(Dispatchers.IO) { - try { - val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(appContext) - adInfo.id - } catch (e: Exception) { - Log.e("OpenAttribution", "Error retrieving GAID: ${e.message}") - null - } - } + val eventId = inputData.getString(EVENT_ID_KEY) ?: return Result.failure() + return trackEvent(appContext, eventId) + } - val (url, trackingEvent) = constructTrackingRequest( - baseUrl, - appendedPackageName, - myOaUid, - gaid, - androidId ?: "unknown", - eventId, - myUid, - myTimestamp - ) + companion object { + private const val EVENT_ID_KEY = "event_id" - val client = OkHttpClient.Builder() - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(10, TimeUnit.SECONDS) - .writeTimeout(10, TimeUnit.SECONDS) + fun createWorkRequest(eventId: String): androidx.work.OneTimeWorkRequest { + val inputData = Data.Builder() + .putString(EVENT_ID_KEY, eventId) .build() - val jsonBody = JSONObject().apply { - put("oa_uid", trackingEvent.oaUid) - put("ifa", trackingEvent.ifa) - put("android_id", trackingEvent.androidId) - put("event_id", trackingEvent.eventId) - put("event_uid", trackingEvent.eventUid) - put("event_time", trackingEvent.eventTime) - } - - val request = Request.Builder() - .url(url) - .post(jsonBody.toString().toRequestBody("application/json".toMediaType())) + return androidx.work.OneTimeWorkRequestBuilder() + .setInputData(inputData) .build() + } - withContext(Dispatchers.IO) { - client.newCall(request).execute() - }.use { response -> - return if (response.isSuccessful) { - Log.i("OpenAttribution", "Tracking request successful: ${response.code}") - Result.success() + suspend fun trackEvent(context: Context, eventId: String): Result { + Log.d("TrackEventWorker", "Tracking event: $eventId") + val userIdManager = UserIdManager.getInstance(context) + val baseUrl = OpenAttribution.getBaseUrl() + + try { + val androidId = + Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) + val myTimestamp = System.currentTimeMillis() + val myUid = UUID.randomUUID().toString() + val myOaUid = userIdManager.getUserId() + val basePackageName = context.packageName + val appendedPackageName = if (EmulatorDetector.isEmulator()) { + "${basePackageName}_test" } else { - Log.w("OpenAttribution", "Tracking request failed: ${response.code}") - Result.retry() + basePackageName } + + val gaid = withContext(Dispatchers.IO) { + try { + val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context) + adInfo.id + } catch (e: Exception) { + Log.e("OpenAttribution", "Error retrieving GAID: ${e.message}") + null + } + } + + val (url, trackingEvent) = constructTrackingRequest( + baseUrl, + appendedPackageName, + myOaUid, + gaid, + androidId ?: "unknown", + eventId, + myUid, + myTimestamp + ) + + val client = OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .build() + + val jsonBody = JSONObject().apply { + put("oa_uid", trackingEvent.oaUid) + put("ifa", trackingEvent.ifa) + put("android_id", trackingEvent.androidId) + put("event_id", trackingEvent.eventId) + put("event_uid", trackingEvent.eventUid) + put("event_time", trackingEvent.eventTime) + } + + val request = Request.Builder() + .url(url) + .post(jsonBody.toString().toRequestBody("application/json".toMediaType())) + .build() + + withContext(Dispatchers.IO) { + client.newCall(request).execute() + }.use { response -> + return if (response.isSuccessful) { + Log.i("OpenAttribution", "Tracking request successful: ${response.code}") + Result.success() + } else { + Log.w("OpenAttribution", "Tracking request failed: ${response.code}") + Result.retry() + } + } + } catch (e: Exception) { + Log.e("OpenAttribution", "Error in TrackAppOpenWorker: ${e.message}") + return Result.retry() } - } catch (e: Exception) { - Log.e("OpenAttribution", "Error in TrackAppOpenWorker: ${e.message}") - return Result.retry() } - } - private fun constructTrackingRequest( - baseUrl: String, - packageName: String, - oauid: String, - gaid: String?, - androidId: String, - eventId: String, - eventUid: String, - eventTime: Long - ): Pair { - val url = "$baseUrl/collect/events/$packageName" - - val trackingEvent = TrackingEvent( - oaUid = oauid, - ifa = gaid, - androidId = androidId, - eventId = eventId, - eventUid = eventUid, - eventTime = eventTime - ) - - Log.i("OpenAttribution", "Constructing tracking URL $url") - return Pair(url, trackingEvent) + private fun constructTrackingRequest( + baseUrl: String, + packageName: String, + oauid: String, + gaid: String?, + androidId: String, + eventId: String, + eventUid: String, + eventTime: Long + ): Pair { + val url = "$baseUrl/collect/events/$packageName" + + val trackingEvent = TrackingEvent( + oaUid = oauid, + ifa = gaid, + androidId = androidId, + eventId = eventId, + eventUid = eventUid, + eventTime = eventTime + ) + + Log.i("OpenAttribution", "Constructing tracking URL $url") + return Pair(url, trackingEvent) + } } } diff --git a/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt b/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt index ec4863e..6545a25 100644 --- a/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt +++ b/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt @@ -106,10 +106,17 @@ class OpenAttribution private constructor(private val context: Context) { } private fun scheduleTrackAppOpen() { - val workRequest = OneTimeWorkRequestBuilder() - .build() + val workRequest = TrackEventWorker.createWorkRequest("app_open") + WorkManager.getInstance(context).enqueue(workRequest) + + } + + fun trackEvent(eventId: String) { + val workRequest = TrackEventWorker.createWorkRequest(eventId) WorkManager.getInstance(context).enqueue(workRequest) } -} \ No newline at end of file +} + + diff --git a/OpenAttribution/src/main/java/dev/openattribution/sdk/TrackingEvent.kt b/OpenAttribution/src/main/java/dev/openattribution/sdk/TrackingEvent.kt new file mode 100644 index 0000000..c398fd4 --- /dev/null +++ b/OpenAttribution/src/main/java/dev/openattribution/sdk/TrackingEvent.kt @@ -0,0 +1,12 @@ +package dev.openattribution.sdk + +data class TrackingEvent( + val oaUid: String, + val ifa: String?, + val androidId: String, + val eventId: String, + val eventUid: String, + val eventTime: Long, + val customProperties: Map? = null +) + From e3f9448ae4d2e73afe6a345f7cae573a201666d3 Mon Sep 17 00:00:00 2001 From: James O'Claire Date: Tue, 25 Feb 2025 17:26:32 +0800 Subject: [PATCH 2/6] WIP: Added trackEvent which takes a single string evenId --- CHANGELOG.md | 9 +++ .../java/dev/openattribution/sdk/OAWorker.kt | 6 ++ .../java/dev/openattribution/sdk/Track.kt | 59 +++++++++---------- gradle/libs.versions.toml | 2 +- 4 files changed, 43 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 955f172..627e3f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Change Log +## 0.0.10 *(2025-02-25)* + +### THIS SDK IS NOT YET STABLE +Please feel free to try but note that this is a work in progress. Feel free to join Open Attribution for questions or contributing. + +### Changes +- Record events + + ## 0.0.9 *(2025-02-13)* ### THIS SDK IS NOT YET STABLE diff --git a/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt b/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt index 8b87c91..4f50f76 100644 --- a/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt +++ b/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt @@ -19,6 +19,10 @@ import java.util.concurrent.TimeUnit + + + + class TrackEventWorker( private val appContext: Context, workerParams: WorkerParameters @@ -34,6 +38,8 @@ class TrackEventWorker( companion object { private const val EVENT_ID_KEY = "event_id" + + fun createWorkRequest(eventId: String): androidx.work.OneTimeWorkRequest { val inputData = Data.Builder() .putString(EVENT_ID_KEY, eventId) diff --git a/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt b/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt index 6545a25..a8aad1b 100644 --- a/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt +++ b/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt @@ -3,15 +3,8 @@ package dev.openattribution.sdk import android.content.Context import android.content.SharedPreferences import android.os.Build -import android.provider.Settings import android.util.Log -import com.google.android.gms.ads.identifier.AdvertisingIdClient -import kotlinx.coroutines.* -import okhttp3.OkHttpClient -import okhttp3.Request import java.util.UUID -import java.util.concurrent.TimeUnit -import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager @@ -82,41 +75,43 @@ object EmulatorDetector { class OpenAttribution private constructor(private val context: Context) { - companion object { - private var myBaseUrl: String? = null - - // Method to initialize the SDK - fun initialize(context: Context, baseUrl: String): OpenAttribution { - - Log.d("MyOA", "InitStart") - - - val instance = OpenAttribution(context) - instance.scheduleTrackAppOpen() + companion object { + private var myBaseUrl: String? = null + private var instance: OpenAttribution? = null + + // Method to initialize the SDK + fun initialize(context: Context, baseUrl: String): OpenAttribution { + Log.d("MyOA", "InitStart") + myBaseUrl = baseUrl + instance = OpenAttribution(context.applicationContext) + instance?.scheduleTrackAppOpen() + return instance!! + } - myBaseUrl = baseUrl -// return OpenAttribution(context) - return instance + // Static method to log events (similar to AppsFlyer) + fun trackEvent(context: Context, eventName: String) { + if (instance == null) { + // Auto-initialize with default URL if not initialized + // Or throw an error requiring initialization first + throw IllegalStateException("OpenAttribution not initialized. Call OpenAttribution.initialize() first.") } - // Getter for the base URL to ensure it's set - fun getBaseUrl(): String { - return myBaseUrl ?: throw IllegalStateException("Base URL is not yet initialized. Call OpenAttribution.initialize() first.") - } + // Track the event with optional parameters + val workRequest = TrackEventWorker.createWorkRequest(eventName) + WorkManager.getInstance(context).enqueue(workRequest) } - private fun scheduleTrackAppOpen() { - val workRequest = TrackEventWorker.createWorkRequest("app_open") - WorkManager.getInstance(context).enqueue(workRequest) + // Getter for the base URL to ensure it's set + fun getBaseUrl(): String { + return myBaseUrl ?: throw IllegalStateException("Base URL is not yet initialized. Call OpenAttribution.initialize() first.") + } } - fun trackEvent(eventId: String) { - val workRequest = TrackEventWorker.createWorkRequest(eventId) + private fun scheduleTrackAppOpen() { + val workRequest = TrackEventWorker.createWorkRequest("app_open") WorkManager.getInstance(context).enqueue(workRequest) } - } - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7cda1ca..c888fcc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -oaReleaseVersion = "0.0.9" +oaReleaseVersion = "0.0.10" agp = "8.7.2" kotlin = "1.9.24" coreKtx = "1.15.0" From 45d7a88a41ca5550e48f855a1f2123cd92bbaf6f Mon Sep 17 00:00:00 2001 From: James O'Claire Date: Thu, 27 Feb 2025 14:05:19 +0800 Subject: [PATCH 3/6] Remove comments --- CHANGELOG.md | 2 +- .../src/main/java/dev/openattribution/sdk/Track.kt | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 627e3f0..de9afc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Please feel free to try but note that this is a work in progress. Feel free to join Open Attribution for questions or contributing. ### Changes -- Record events +- Record events with trackEvent ## 0.0.9 *(2025-02-13)* diff --git a/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt b/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt index a8aad1b..25cf4dd 100644 --- a/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt +++ b/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt @@ -88,15 +88,10 @@ class OpenAttribution private constructor(private val context: Context) { return instance!! } - // Static method to log events (similar to AppsFlyer) fun trackEvent(context: Context, eventName: String) { if (instance == null) { - // Auto-initialize with default URL if not initialized - // Or throw an error requiring initialization first throw IllegalStateException("OpenAttribution not initialized. Call OpenAttribution.initialize() first.") } - - // Track the event with optional parameters val workRequest = TrackEventWorker.createWorkRequest(eventName) WorkManager.getInstance(context).enqueue(workRequest) } From c66ccb0727e9b2d3532c672ed62de8d3d1fdbfe1 Mon Sep 17 00:00:00 2001 From: James O'Claire Date: Thu, 27 Feb 2025 17:09:58 +0800 Subject: [PATCH 4/6] Added trackPurchase for tracking revenue events --- .../java/dev/openattribution/sdk/OAWorker.kt | 35 +++++++++++++++++-- .../java/dev/openattribution/sdk/Track.kt | 14 ++++++-- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt b/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt index 4f50f76..3ff13ea 100644 --- a/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt +++ b/OpenAttribution/src/main/java/dev/openattribution/sdk/OAWorker.kt @@ -32,17 +32,37 @@ class TrackEventWorker( Log.d("MyOA", "doWork start") val eventId = inputData.getString(EVENT_ID_KEY) ?: return Result.failure() - return trackEvent(appContext, eventId) + val revenueAmount = if (inputData.keyValueMap.containsKey(EVENT_REVENUE_KEY)) { + inputData.getString(EVENT_REVENUE_KEY) + } else { + null + } + val currency = inputData.getString(EVENT_CURRENCY_KEY) + + return trackEvent(appContext, eventId, revenueAmount, currency) + } companion object { private const val EVENT_ID_KEY = "event_id" + private const val EVENT_REVENUE_KEY = "revenue" + private const val EVENT_CURRENCY_KEY = "currency" + fun createWorkRequest(eventId: String): androidx.work.OneTimeWorkRequest { + val inputData = Data.Builder() + .putString(EVENT_ID_KEY, eventId) + .build() + return androidx.work.OneTimeWorkRequestBuilder() + .setInputData(inputData) + .build() + } - fun createWorkRequest(eventId: String): androidx.work.OneTimeWorkRequest { + fun createRevenueWorkRequest(eventId: String, revenueAmount: Double, currency: String): androidx.work.OneTimeWorkRequest { val inputData = Data.Builder() .putString(EVENT_ID_KEY, eventId) + .putString(EVENT_REVENUE_KEY, revenueAmount.toString()) + .putString(EVENT_CURRENCY_KEY, currency) .build() return androidx.work.OneTimeWorkRequestBuilder() @@ -50,7 +70,7 @@ class TrackEventWorker( .build() } - suspend fun trackEvent(context: Context, eventId: String): Result { + suspend fun trackEvent(context: Context, eventId: String, revenueAmount: String? = null, currency: String? = null): Result { Log.d("TrackEventWorker", "Tracking event: $eventId") val userIdManager = UserIdManager.getInstance(context) val baseUrl = OpenAttribution.getBaseUrl() @@ -102,6 +122,15 @@ class TrackEventWorker( put("event_id", trackingEvent.eventId) put("event_uid", trackingEvent.eventUid) put("event_time", trackingEvent.eventTime) + + + revenueAmount?.let { + put("revenue", it) + put( + "currency", + currency ?: "USD" + ) + } } val request = Request.Builder() diff --git a/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt b/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt index 25cf4dd..e24b556 100644 --- a/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt +++ b/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt @@ -88,7 +88,8 @@ class OpenAttribution private constructor(private val context: Context) { return instance!! } - fun trackEvent(context: Context, eventName: String) { + + fun trackEvent(context: Context, eventName: String, value:Number) { if (instance == null) { throw IllegalStateException("OpenAttribution not initialized. Call OpenAttribution.initialize() first.") } @@ -97,7 +98,16 @@ class OpenAttribution private constructor(private val context: Context) { } - // Getter for the base URL to ensure it's set + fun trackPurchase(context: Context, revenueAmount: Double, currency: String, eventName: String="iap_purchase") { + if (instance == null) { + Log.w("OpenAttribution", "SDK not initialized. Auto-initializing with default settings.") + initialize(context, "https://default-endpoint.openattribution.dev") + } + val workRequest = TrackEventWorker.createRevenueWorkRequest(eventName, revenueAmount, currency) + WorkManager.getInstance(context).enqueue(workRequest) + } + + fun getBaseUrl(): String { return myBaseUrl ?: throw IllegalStateException("Base URL is not yet initialized. Call OpenAttribution.initialize() first.") } From 71f723b811e6a4b517ceca37e337508e622df271 Mon Sep 17 00:00:00 2001 From: James O'Claire Date: Wed, 5 Mar 2025 16:02:54 +0800 Subject: [PATCH 5/6] Final commit --- CHANGELOG.md | 3 ++- .../src/main/java/dev/openattribution/sdk/Track.kt | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de9afc6..3205310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ # Change Log -## 0.0.10 *(2025-02-25)* +## 0.0.10 *(2025-03-05)* ### THIS SDK IS NOT YET STABLE Please feel free to try but note that this is a work in progress. Feel free to join Open Attribution for questions or contributing. ### Changes - Record events with trackEvent +- Record purchases with trackPurchase ## 0.0.9 *(2025-02-13)* diff --git a/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt b/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt index e24b556..88d96a3 100644 --- a/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt +++ b/OpenAttribution/src/main/java/dev/openattribution/sdk/Track.kt @@ -88,8 +88,7 @@ class OpenAttribution private constructor(private val context: Context) { return instance!! } - - fun trackEvent(context: Context, eventName: String, value:Number) { + fun trackEvent(context: Context, eventName: String) { if (instance == null) { throw IllegalStateException("OpenAttribution not initialized. Call OpenAttribution.initialize() first.") } @@ -97,11 +96,9 @@ class OpenAttribution private constructor(private val context: Context) { WorkManager.getInstance(context).enqueue(workRequest) } - fun trackPurchase(context: Context, revenueAmount: Double, currency: String, eventName: String="iap_purchase") { if (instance == null) { Log.w("OpenAttribution", "SDK not initialized. Auto-initializing with default settings.") - initialize(context, "https://default-endpoint.openattribution.dev") } val workRequest = TrackEventWorker.createRevenueWorkRequest(eventName, revenueAmount, currency) WorkManager.getInstance(context).enqueue(workRequest) From 90b63a5a6421bbd62f7db1cdb52995a71bd55659 Mon Sep 17 00:00:00 2001 From: James O'Claire Date: Wed, 5 Mar 2025 16:30:22 +0800 Subject: [PATCH 6/6] Add README documentation --- README.md | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c7625e6..e4ea529 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ To have a fully functional SDK which can be used to track and attribute installs - [x] Library installable via Maven - [x] user input server endpoint ie `https://demo.openattribution.dev` -- [ ] Events: +- [x] Events: - [x] app_open tracking and attributing - - [ ] Basic event tracking - - [ ] Basic revenue tracking + - [x] Basic event tracking + - [x] Basic revenue tracking - [ ] Documentation for how to use and next steps ## Install @@ -52,14 +52,48 @@ class MyApplication : Application() { super.onCreate() // Initialize the OpenAttribution SDK, replace with your domain openAttribution = OpenAttribution.initialize(this, "https://demo.openattribution.dev") - } - } ``` +## Tracking Events + +Use `trackEvent` to log custom events in your app. + +| Parameter | Type | Description | +| ----------- | --------- | --------------------------------------------------------- | +| `context` | `Context` | Android context (e.g., Activity or Application). | +| `eventName` | `String` | Name of the custom event to track (e.g., "button_click"). | + + +Example use: +```kotlin + import dev.openattribution.sdk.OpenAttribution + + ... + + OpenAttribution.trackEvent(context, "my_event_name") + +``` + + + +## Track Purchases + +Use `trackPurchase` to track revenue for In App Purchases in your app. +| Parameter | Type | Description | Default | +|-----------------|----------|--------------------------------------------------|------------------| +| `context` | `Context`| Android context (e.g., Activity or Application). | - | +| `revenueAmount` | `Double` | Purchase amount (e.g., 12.34). | - | +| `currency` | `String` | ISO 4217 currency code (e.g., "USD"). | - | +| `eventName` | `String` | Event name for tracking. | `"iap_purchase"` | + + +```kotlin + OpenAttribution.trackPurchase(context, 12.34, "USD") +``` ## Local Development: