# 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"
)
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.roivenue.com/roivenue-resources/howto/roivenue-measurement-implementation/tracking-in-android-applications.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
