Fire the call right after the platform billing API confirms the trial transaction. Avoid passing the trial duration, the plan ID, or the payment-method type — your billing vendor (App Store Connect, Google Play, Stripe, RevenueCat) already has all of those with authoritative timestamps.
▸Install the Kotlin (Android) SDK
// build.gradle.kts (app module)
dependencies {
implementation("com.respectlytics:respectlytics-kotlin:3.0.0")
}
Pure Kotlin coroutines implementation. No Java dependencies, no Google Play Services dependencies. ~300KB DEX overhead — compare to roughly 3.8MB for Firebase Analytics (a measurable cold-start improvement on lower-end devices).
▸Initialize Respectlytics in Kotlin (Android)
import com.respectlytics.android.Respectlytics
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Respectlytics.configure(this, appKey = "<YOUR_APP_KEY>")
}
}
Initialize once in Application.onCreate. No additional permissions in the manifest — INTERNET is sufficient. The SDK does not request AD_ID, does not query AdvertisingIdClient, and does not declare ACCESS_NETWORK_STATE.
▸Track the event in Kotlin (Android)
import com.android.billingclient.api.*
import com.respectlytics.android.Respectlytics
class TrialStartHandler : PurchasesUpdatedListener {
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) return
purchases?.forEach { purchase ->
// Trial offers are flagged on the SubscriptionOfferDetails the buyer chose.
val isTrialOffer = purchase.products.any { /* check offer details */ true }
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED && isTrialOffer) {
Respectlytics.track("trial_start")
}
}
}
}
Distinguishing the trial offer requires correlating the purchase against the offerToken you passed during launchBillingFlow — the BillingClient doesn't echo it back. Most apps maintain that mapping in their pre-purchase state.
✦Privacy & implementation notes
Your billing system (App Store Connect, Google Play Billing, RevenueCat, Stripe) is the system of record for plan IDs, durations, payment methods, and renewal states. Duplicating that data into your analytics pipeline produces two truths that drift over time. The Respectlytics 5-field schema simply refuses the duplication — by design.
Cohort retention curves ("of the trials started in March, what % were paying in May?") are most accurately computed against your billing data, not your product analytics. Respectlytics tells you product engagement; your billing system tells you revenue truth. Keep them separate.
Many teams discover the com.google.android.gms.permission.AD_ID permission in their merged manifest only after Google Play flags them — usually because a transitive dependency dragged it in. Respectlytics's Kotlin SDK has no Google Play Services dependency at all, so it cannot contribute to that merge.
The SDK is implemented as pure Kotlin coroutines with no Java sources, no RxJava, and no platform channels. Events are queued in a Channel<Event> buffered to a small ring (RAM-only), drained by a coroutine that flushes every 30 seconds or on backgrounding. There is no SharedPreferences usage.
⇋How this compares to other analytics SDKs
| Trial start event | Firebase Analytics | Mixpanel | Respectlytics |
|---|---|---|---|
| Per-user identity | app_instance_id + user_id | distinct_id | Never |
| Plan ID / trial duration | Recommended | Recommended | Rejected by API |
| Payment method type | Recommended | Recommended | Rejected by API |
| Trial-to-paid attribution | Per-user | Per-user (Identity Merging) | Session-scoped or via billing system |
| Cohort retention curve | Per-user | Per-user | Out of scope (use billing data) |
❓Frequently asked questions
How do we compute trial-to-paid conversion rate?
Either by session (a session that emits trial_start and later trial_conversion is converting), or — for the authoritative number — by querying your billing system. Most boards want the billing-system number for revenue, and the session-rate number for product diagnosis.
What about distinguishing 7-day vs 14-day trial cohorts?
If you offer multiple trial lengths, emit distinct event names: trial_start_7d, trial_start_14d. The aggregation gives you per-cohort conversion rate. Don't pass the duration as a parameter — the API rejects it.
Should we tag the source (organic / paid / referral)?
Distinct event names per source — trial_start_organic, trial_start_paid. Keep to your top 3–5 sources; bucket the rest as trial_start_other.
When do we fire — at button tap, or at billing confirmation?
At billing confirmation. Tap-fire produces inflated numbers when users abandon the platform billing prompt; confirmation-fire produces the truthful trial-start rate. The few extra seconds of wait are worth a non-noisy metric.