# Android (https://www.getfounderhq.com/docs/analytics/sdks/android)

Capture screens, app lifecycle, and your own events from a Kotlin app.

<Callout title="Before you start">
You need a publishable key (`fhq_pk_...`) for the brand you want to
measure. See [Getting started](/analytics/getting-started).
</Callout>

## Install

```kotlin
dependencies {
    implementation("com.getfounderhq:events:0.8.0")
    // Navigation Compose screens
    implementation("com.getfounderhq:events-compose:0.8.0")
}
```

The SDK needs `minSdk` 24 and Java 17.

## Initialize

Create one client in your `Application`, and share it.

```kotlin
class MyApplication : Application() {
    lateinit var events: FounderHQEvents

    override fun onCreate() {
        super.onCreate()
        events = FounderHQEvents(this, "fhq_pk_XXXX")
    }
}
```

Pass a config when you want to change the defaults:

```kotlin
events = FounderHQEvents(
    this,
    "fhq_pk_XXXX",
    FounderHQEventsConfig(captureScreens = true),
)
```

The client registers activity lifecycle callbacks, captures activity
screens and application lifecycle, and records the session start.

## Capture events

```kotlin
events.capture("trial.started", mapOf("plan" to "growth"))
```

`capture` waits for startup to finish, then queues the event. Call
`readyForCapture()` when you want to wait for startup on its own.

Record a deep link when your app opens from one:

```kotlin
events.captureDeepLink(url)
```

`captureDeepLink` drops the query and fragment, and keeps the campaign
parameters it recognizes. After you read Play Install Referrer data, pass
it on:

```kotlin
events.captureInstallReferrer(mapOf("utm_source" to "google_play"))
```

## Track screens

Activity screens are captured for you. Call `events.screen("Pricing")`
anywhere else.

With Navigation Compose, add the observer inside your `NavHost` scope:

```kotlin
import com.founderhq.events.compose.FounderHQNavigationObserver

FounderHQNavigationObserver(navController = navController, events = events)
```

It records the current route, and skips a repeat of the route the app is
already on.

## Identify a contact

```kotlin
events.identify("user_42", mapOf("email" to "jane@acme.com"))
```

Change identity and account in one call:

```kotlin
events.identify(
    "user_42",
    mapOf("email" to "jane@acme.com"),
    FounderHQAccountContext("workspace_123"),
)
```

Update contact properties on their own with `setPersonProperties(set,
setOnce)`. With the default `IDENTIFIED_ONLY` mode, changes made before
`identify` stay on the device and go out with the identify call.

Call `reset()` on logout. It clears the identity and the account.

## Accounts

```kotlin
events.setAccount("workspace_123", mapOf("plan" to "growth"))
events.setAccountProperties(mapOf("seats" to 12))
events.clearAccount() // resetAccounts() does the same
```

`setAccount` also takes a `FounderHQAccountContext`, which carries `key`,
`properties`, and `contextToken`. Set `account` in the config when the first
automatic event must already carry it. An account change rotates the
account span only. The person session and queued events are untouched. See
[Accounts and groups](/analytics/concepts/accounts-and-groups).

## Consent

| You call | What happens |
| --- | --- |
| `optIn()` | Capture is on |
| `optOut()` | Capture stops, and the queue is cleared |
| `isOptedOut()` | Returns `true` while capture is off |

Set `optOutByDefault = true` in the config to start silent until the person
agrees. See [Consent and privacy](/analytics/concepts/consent-and-privacy).

The SDK never collects advertising identifiers.

## Purchases

The client can tie a Play Billing or RevenueCat purchase to the person who
made it.

| Method | What it does |
| --- | --- |
| `purchaseAttribution()` | Returns the identifiers to pass to the store or to RevenueCat |
| `preparePurchase(source, completion)` | Records intent before checkout, and returns the purchase context |
| `applyPurchaseContext(builder, prepared)` | Puts the context on your Play Billing flow |
| `observePurchase(purchase, prepared)` | Reports the purchase that followed |
| `claimSubscription(purchase, confirmation)` | Moves future revenue of an existing subscription |

The full flow, including what each store needs, is on
[Mobile purchase claims](/analytics/revenue/mobile-purchase-claims).

## Config

`FounderHQEventsConfig` takes these values.

| Value | Type | Default | What it does |
| --- | --- | --- | --- |
| `host` | `String` | `https://i.getfounderhq.com` | Where events are sent |
| `flushAt` | `Int` | `20` | Sends a batch once this many events are queued |
| `flushIntervalSeconds` | `Long` | `10` | Sends a batch on this timer. `0` turns the timer off |
| `personProfiles` | `PersonProfiles` | `IDENTIFIED_ONLY` | When contact property changes are applied |
| `optOutByDefault` | `Boolean` | `false` | Starts opted out |
| `captureLifecycle` | `Boolean` | `true` | App opened and backgrounded events |
| `captureScreens` | `Boolean` | `true` | Activity screen events |
| `captureSessions` | `Boolean` | `true` | Session start events |
| `captureInstallUpdates` | `Boolean` | `true` | App installed and updated events |
| `remoteConfig` | `Boolean` | `true` | Reads capture settings you set in FounderHQ |
| `account` | `FounderHQAccountContext?` | `null` | Installs account context before the first event |
| `purchasePrepareTimeoutMillis` | `Long` | `3000` | How long `preparePurchase` waits before checkout goes ahead offline |
| `captureElementInteractions` | `Boolean` | `false` | Taps and rage taps. See [Element interactions](#element-interactions) |
| `capturePushNotificationOpened` | `Boolean` | `true` | Records the notification taps your app reports |
| `tracingHeaders` | `List<String>?` | `null` | Hostnames whose requests carry the session id |
| `maxQueueSize` | `Int` | `1000` | Events kept offline before the oldest are dropped |
| `eventTtlMillis` | `Long` | `86400000` | How long an unsent event may wait |
| `maxRetries` | `Int` | `5` | Times a failed event is retried |
| `debug` | `Boolean` | `false` | Logs what the SDK drops or refuses |

`FounderHQEventsDependencies` replaces the clock, UUID source, storage,
transport, and platform facts. Use it in tests.

## Sending

| Method | Returns | What it does |
| --- | --- | --- |
| `flush()` | `Boolean` | Sends queued events now, and tells you whether the queue drained |
| `close()` | — | Flushes, stops the executors, and unregisters the lifecycle callbacks |
| `getDistinctId()` | `String` | The current identity |
| `getSessionId()` | `String` | The current session |

`FounderHQEvents` implements `AutoCloseable`.

## Element interactions

Set `captureElementInteractions` to `true`. The SDK then records
`$autocapture` when someone taps a control, and `$rageclick` when they tap the
same control three times in a second. Both are off by default.

Each event carries the control and up to four of its parents.

| Stored | Not stored |
| --- | --- |
| The view class, resource entry name, and content description | Anything a person typed |
| The path through the view hierarchy | Tap coordinates |
| The label a `Button` shows, or the selected tab of a `TabLayout` | The contents or hint of an `EditText` |

A tap on an `EditText` records nothing at all, and a plain `TextView` never
contributes its words. Give a view the tag `android:tag="fhq-no-capture"` to
skip it and everything inside it.

Labels are cut to 255 characters. Numbers that look like a card number or a
social security number are removed from them.

Only a tap counts. A finger that moves further than the platform's touch slop,
a second finger, or a cancelled touch means the gesture was a scroll. Lifting
a finger over a row at the end of a scroll records nothing.

## Remote config

The client reads your capture settings from FounderHQ at startup, caches
them in its storage, and applies the cached copy on the next launch.

| Key | What it turns on and off |
| --- | --- |
| `capture_sessions` | Session start events |
| `capture_screens` | Screen events |
| `capture_lifecycle` | App opened and backgrounded events |
| `autocapture` | Taps and rage taps |
| `capture_rageclicks` | Rage taps only. Taps are still recorded |

Your settings in FounderHQ win. They can turn capture off for every install,
and on for an app that shipped with the wrong value. You cannot rebuild an app
people have already installed, so the dashboard decides. The values in your
config apply until your settings arrive.

Events already queued under a key that turns off are dropped before they leave
the device.

Call `refreshRemoteConfig()` to fetch them again while the app runs. Call
`applyRemoteConfig(remote)` to apply settings you supply yourself. Set
`remoteConfig = false` in the config to skip the request.

## Next

Read the [protocol reference](/analytics/protocol-reference) for the wire
format, the reserved event names, and the campaign properties a deep link
can carry.
