# Kotlin (Android)

For native Android apps, minSdk 24. No Google Play services, no dependencies, about 65 KB. Compose screens come in a second, optional library.

## Install

```kotlin
// app/build.gradle.kts
implementation("fyi.apparent:apparent-android:0.2.0")
```

With Compose, add the screens library too (same version, also on Maven Central):

```kotlin app/build.gradle.kts
implementation("fyi.apparent:apparent-compose:0.2.1")
```

Optional: Play's install referrer library, so Apparent knows which link brought each install.

```kotlin app/build.gradle.kts
implementation("com.android.installreferrer:installreferrer:2.2")
```

## Start it

In your Application class's onCreate:

```kotlin
import fyi.apparent.Apparent

Apparent.start(this, app = "<app id>", key = "<write key>", apiUrl = "https://in.apparent.fyi")
```

Add `disabled = BuildConfig.DEBUG` if debug builds shouldn't send. Emulators and Google's test phones are left out of the numbers by themselves.

## Screens

Compose (the `apparent-compose` library), at the top of each screen:

```kotlin
@Composable fun Paywall() { TrackScreen("paywall"); /* … */ }
```

Jetpack Navigation, once:

```kotlin
val entry by navController.currentBackStackEntryAsState()
TrackNavigation(entry?.destination?.route)   // "practice/{topic}" → "practice/[topic]"
```

Without Compose: `Apparent.screen("paywall")`, or `ApparentConfig(…, trackActivities = true)` for one screen per Activity.

## Events and users

```kotlin
Apparent.track("paywall_view", mapOf("source" to "onboarding"))
Apparent.setProperties(mapOf("plan" to "free"))   // on every later event; null removes one
Apparent.identify(user.id)                        // after sign-in
Apparent.logout()                                 // on sign-out
```

Calls work from any thread and never block the main thread. More in [Events and users](https://apparent.fyi/docs/events).

## Purchases

```kotlin
Apparent.linkRevenueCat { Purchases.sharedInstance.setAttributes(it) }   // with RevenueCat
Apparent.trackPurchase("pro_yearly", 29.99, "EUR")                       // Play Billing, without RevenueCat
```

## Links and notifications

The link (App Link or your own scheme) or Firebase notification tap that launches an Activity is sent on its own, as `deep_link_opened` and `push_opened`. For the ones that bring a running Activity back, pass the Intent on:

```kotlin
override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    Apparent.handleIntent(intent)
}
```

Notifications you show yourself (a Firebase message while the app is open, a local notification): `Apparent.trackPushOpened(message.data, message.messageId)` when it's tapped. Links you route yourself: `Apparent.trackDeepLink(url)`.

- Secrets in a link (`token`, `code`, `password`, `key`, `auth`, `session`, `sig`…) are sent as `[redacted]`, and `utm_source`, `utm_medium`, `utm_campaign` come out as their own fields. A campaign link that opens the app on its first day counts as where that person came from.
- `push_opened` carries only `campaign` (or `utm_campaign`) and `title` from the notification's **data**, never what it said.
- Off: `ApparentConfig(…, trackDeepLinks = false, trackPushOpens = false)`.

## Your own filter

`beforeSend` sees every event before it is saved. Return it changed, or `null` to drop it:

```kotlin
Apparent.start(this, ApparentConfig(app = "einb", key = "…", beforeSend = { event ->
    if (event.name == "debug_tap") null
    else event.copy(properties = event.properties?.minus("email"))
}))
```

If it throws, the event is kept as it was.

## Errors and opting out

- Uncaught exceptions are saved before the app goes down and sent on the next launch.
- Errors you catch: `Apparent.captureError(e)`.
- `Apparent.optOut()` / `Apparent.optIn()`, remembered.
- Ask for consent first? Start with `ApparentConfig(…, optOutByDefault = true)` and call `Apparent.optIn()` once they agree. Nothing is collected before that.
- Nothing showing up? In a debug build the SDK prints `Apparent: app id or key is wrong — events are not being saved` once when the app id or key doesn't match. Copy both again from Connect.
- Simulators and emulators say so themselves (`simulator` in each batch), so they're never counted, even in a release build.

## Good to know

- The SDK never reads the advertising id, so you don't need the `AD_ID` permission.
- Its state lives in `noBackupFilesDir`: a phone restored from a backup counts as a new install.
- R8 rules come with the library.
- It never throws into your app: a `start` without an app id or key is ignored with a Logcat warning, and odd property values (dates, big numbers, lists) are turned into plain ones.
- Data safety answers: [Privacy answers](https://apparent.fyi/docs/privacy).
