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
// 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):
implementation("fyi.apparent:apparent-compose:0.2.1")Optional: Play's install referrer library, so Apparent knows which link brought each install.
implementation("com.android.installreferrer:installreferrer:2.2")Start it
In your Application class's onCreate:
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:
@Composable fun Paywall() { TrackScreen("paywall"); /* … */ }Jetpack Navigation, once:
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
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-outCalls work from any thread and never block the main thread. More in Events and users.
Purchases
Apparent.linkRevenueCat { Purchases.sharedInstance.setAttributes(it) } // with RevenueCat
Apparent.trackPurchase("pro_yearly", 29.99, "EUR") // Play Billing, without RevenueCatLinks 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:
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], andutm_source,utm_medium,utm_campaigncome out as their own fields. A campaign link that opens the app on its first day counts as where that person came from. push_openedcarries onlycampaign(orutm_campaign) andtitlefrom 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:
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 callApparent.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 savedonce when the app id or key doesn't match. Copy both again from Connect. - Simulators and emulators say so themselves (
simulatorin 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_IDpermission. - 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
startwithout 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.