# Tracking in Android Applications

### Prerequisites

Android Studio Hedgehog (2024+)

Kotlin 1.9+

Min SDK 24+

Internet permission in `AndroidManifest.xml`

```xml
<uses-permission android:name="android.permission.INTERNET"/>
```

### What the Tracking API Expects

The Roivenue endpoint accepts events in JSON format. Each event represents a user interaction and includes basic context (timestamp, device info, URL, etc.) plus optional conversion data.

```
{
  "propertyId": "adbfb3aa-xxxx-xxxx-xxxx-xxxxxxxx",
  "type": "pageview",
  "timestamp": "2024-10-30T07:50:40",
  "url": "https://example.com/?utm_source=facebook",
  "deviceId": "20904e-f1e3-79f2-b5b7-00d6a7bfe57e",
  "userId": "someUserHashed",
  "referer": "$direct",
  "screenWidth": 1170,
  "screenHeight": 2532,
  "utmSource": "facebook",
  "utmMedium": "cpc",
  "utmCampaign": "autumn_sale",
  "conversionType": "purchase",
  "conversionId": "ORD-001",
  "conversionValue": 149.99,
  "currencyCode": "EUR"
}
```

### Capturing and Managing UTMs

UTMs describe the source of the **current session**.\
They are cleared on app cold start and refreshed when the app is opened via a new deep link.

```
package com.roivenue.tracking

import android.net.Uri

object UTMManager {
    private val utmKeys = listOf(
        "utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"
    )

    private val sessionUTMs = mutableMapOf<String, String>()

    fun storeFromUri(uri: Uri) {
        for (key in utmKeys) {
            uri.getQueryParameter(key)?.let { value ->
                sessionUTMs[key] = value
            }
        }
    }

    fun getAll(): Map<String, String> = sessionUTMs.toMap()

    fun clear() = sessionUTMs.clear()
}
```

### Handle Deep Links in `MainActivity`

```
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        UTMManager.clear() // reset on fresh launch
        handleIntent(intent)
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        handleIntent(intent)
    }

    private fun handleIntent(intent: Intent?) {
        val data = intent?.data ?: return
        UTMManager.storeFromUri(data)
    }
}
```

### DeviceId generation

Generate a device ID compliant with **Google Play** and **App Tracking Transparency** rules.&#x20;

```
package com.roivenue.tracking

import android.content.Context
import java.util.*

object DeviceIdManager {
    private const val PREF_NAME = "roivenue_prefs"
    private const val PREF_KEY = "roivenue_device_id"

    fun getOrCreate(context: Context): String {
        val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        var id = prefs.getString(PREF_KEY, null)
        if (id == null) {
            id = UUID.randomUUID().toString()
            prefs.edit().putString(PREF_KEY, id).apply()
        }
        return id
    }
}
```

### RoivenueTracker.kt

```
package com.roivenue.tracking

import android.content.Context
import android.util.DisplayMetrics
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL
import java.text.SimpleDateFormat
import java.util.*

class RoivenueTracker(private val context: Context) {
    private val endpoint = "https://tr.roivenue.com/c"
    private val propertyId = "adbfb3aa-xxxx-xxxx-xxxx-xxxxxxxx"
    private val deviceId = DeviceIdManager.getOrCreate(context)

    fun trackEvent(
        type: String,
        url: String? = null,
        userId: String? = null,
        conversionType: String? = null,
        conversionId: String? = null,
        conversionValue: Double? = null,
        currencyCode: String? = null
    ) {
        val metrics: DisplayMetrics = context.resources.displayMetrics
        val utms = UTMManager.getAll()

        val payload = JSONObject().apply {
            put("propertyId", propertyId)
            put("type", type)
            put("timestamp", isoTimestamp())
            put("url", url)
            put("deviceId", deviceId)
            put("userId", userId)
            put("screenWidth", metrics.widthPixels)
            put("screenHeight", metrics.heightPixels)
            put("conversionType", conversionType)
            put("conversionId", conversionId)
            put("conversionValue", conversionValue)
            put("currencyCode", currencyCode)
            put("utmSource", utms["utm_source"] ?: "\$direct")
            put("utmMedium", utms["utm_medium"])
            put("utmCampaign", utms["utm_campaign"])
            put("utmContent", utms["utm_content"])
            put("utmKeyword", utms["utm_term"])
        }

        sendEvent(payload)
    }

    private fun isoTimestamp(): String {
        val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US)
        format.timeZone = TimeZone.getTimeZone("UTC")
        return format.format(Date())
    }

    private fun sendEvent(json: JSONObject) {
        CoroutineScope(Dispatchers.IO).launch {
            try {
                val url = URL(endpoint)
                val conn = url.openConnection() as HttpURLConnection
                conn.requestMethod = "POST"
                conn.setRequestProperty("Content-Type", "application/json")
                conn.doOutput = true
                conn.outputStream.use { it.write(json.toString().toByteArray()) }
                conn.responseCode
                conn.disconnect()
            } catch (_: Exception) { }
        }
    }
}
```

### Tracking Page Views

```
// Inside Activity or Fragment
override fun onResume() {
    super.onResume()
    RoivenueTracker(this).trackEvent(
        type = "pageview",
        url = "app://home"
    )
}
```

### Tracking Conversions

```
RoivenueTracker(this).trackEvent(
    type = "conversion",
    conversionType = "purchase",
    conversionId = "ORDER-001",
    conversionValue = 149.99,
    currencyCode = "EUR"
)
```
