Connect a Kotlin (Android) app
- appId — the exact app slug, for example my-cool-app
- apiKey — the API key, which starts with hk_ (required in production)
- apiUrl — the hosted dashboard URL: https://saraayab.shop
The app-specific key is included in the mobile build and can be extracted. Treat it as public; never use your dashboard admin password in the app.
File paths are relative to your project root. Use the matching files in your app's folder structure.
Quick path — native Kotlin, no third-party SDKs
Add dependencies and the internet permission
your project/app/build.gradle.ktsdependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
implementation("androidx.lifecycle:lifecycle-process:2.8.4")
}your project/app/src/main/AndroidManifest.xml<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".App"
...>
...
</application>Set BuildConfig values in app/build.gradle.kts
your project/app/build.gradle.ktsandroid {
buildFeatures { buildConfig = true }
defaultConfig {
buildConfigField("String", "TESTER_API_URL", "\"https://saraayab.shop\"")
buildConfigField("String", "TESTER_APP_ID", "\"my-kotlin-app\"")
buildConfigField("String", "TESTER_API_KEY", "\"hk_your_key_here\"")
buildConfigField("String", "TESTER_APP_VERSION", "\"1.0.3\"")
}
}Get the API key from Developer portal → your app → Copy key. The x-api-key header is required in production. appId is the exact appId from the Developer portal. Create the app there first.
Use https://saraayab.shop. HTTPS avoids Android cleartext HTTP blocking.
Create TesterAnalytics.kt
your project/app/src/main/java/com/example/app/TesterAnalytics.ktDownload the complete client Download the complete Kotlin client and save it as app/src/main/java/com/example/app/TesterAnalytics.kt. The client generates a UUID v4 tester ID, sends register/app_opened, tracks lifecycle with ProcessLifecycleOwner, installs an uncaught exception handler, runs a ~60s heartbeat, and queues up to 200 events offline.
Create App.kt and register it in AndroidManifest.xml
your project/app/src/main/java/com/example/app/App.ktpackage com.example.app
import android.app.Application
import org.json.JSONObject
class App : Application() {
override fun onCreate() {
super.onCreate()
val device = JSONObject().apply {
put("os", "android")
put("osVersion", android.os.Build.VERSION.RELEASE)
put("systemVersion", android.os.Build.VERSION.SDK_INT.toString())
put("deviceModel", android.os.Build.MODEL)
put("deviceBrand", android.os.Build.MANUFACTURER)
put("manufacturer", android.os.Build.MANUFACTURER)
put("model", android.os.Build.MODEL)
put("brand", android.os.Build.BRAND)
}
TesterAnalytics.init(
this,
TesterAnalytics.Config(
apiUrl = BuildConfig.TESTER_API_URL,
appId = BuildConfig.TESTER_APP_ID,
apiKey = BuildConfig.TESTER_API_KEY,
appVersion = BuildConfig.TESTER_APP_VERSION,
device = device
)
)
}
}your project/app/src/main/AndroidManifest.xml<application android:name=".App" ... >
Track screens and report bugs
your project/app/src/main/java/com/example/app/MainActivity.ktoverride fun onResume() {
super.onResume()
TesterAnalytics.setScreen("HomeScreen")
}
TesterAnalytics.reportBug(
"Checkout button does nothing",
params = JSONObject().apply {
put("title", "Checkout dead button")
put("steps", "Open cart -> tap Checkout")
put("screenName", "CartScreen")
}
)
val id = TesterAnalytics.getTesterId()
TesterAnalytics.setContactName("Sara")
TesterAnalytics.setOptedOut(true)
TesterAnalytics.eraseMyData()Crash capture is automatic via Thread.setDefaultUncaughtExceptionHandler.
Build, install and verify
Open a terminal at your project root, then run:
gradlew.bat installDebug
On Windows use gradlew.bat installDebug. On macOS/Linux use ./gradlew installDebug.
- Install and open the app once on a device or emulator.
- Events appear under the app you created.
- The tester shows as Active with version, device, opens, and activity history.
Payload reference
POST {apiUrl}/api/events with x-api-key: <app api key>.
- appId — required, lowercase app slug (1–40 letters, numbers, or hyphens)
- testerId — required, UUID v4
- device — strict object: only os, osVersion, systemVersion, deviceModel, deviceBrand, manufacturer, model, brand
- screens — ≤ 10 items, ≤ 80 chars each; message ≤ 500; stack ≤ 2600
- params — values must be string / number / boolean
Events: register, app_opened, app_foreground, app_background, screen_view, crash, bug_report, session_start, session_resume, session_pause, session_heartbeat
Active usage tracking
The client only sends signals (session_start, session_resume, session_pause, session_heartbeat). The server measures elapsed active time with its own clock — it never trusts client timestamps. Background time is excluded because a pause/close signal ends the measured interval.
Force-close / crash / no network:sessions auto-close after a no-signal window, so a lost pause or heartbeat can't inflate totals. Duplicate signals are ignored and a single continuous session is capped to a maximum length.
View it:theUsagetab shows today's totals, per-tester daily history, and date-range sums.
Privacy & opt-out
The client uses a stable anonymous Tester ID (a random UUID stored in SharedPreferences ) — no name, email, phone, or personal information is required. The only optional personal fields are the contact name/channel the tester chooses to enter.
Testers can opt out at any time with setOptedOut(true), and can erase their local + server-side data with eraseMyData().
Privacy policy for a temporary testing app
Add a clear, accurate disclosure to your privacy policy and store listing. Adapt this sample to your actual data, test period, hosting and retention:
During this app's [closed/open testing period], [developer/organization] uses TesterFlow to evaluate test builds, diagnose crashes and bug reports, and understand test-session activity. The app sends a random tester ID, app version, device information, screen/activity events, and foreground session signals to [hosting provider/region]. Crash details and bug-report information are sent when generated or submitted. Testers may optionally provide contact details. This data is used only to operate this testing program, not for advertising or production-user analytics. Collection will stop when [test end condition/date]. Data is retained for [actual retention period]. To stop future collection, use [in-app opt-out instructions]; to request deletion, use [erase instructions or contact details].
Only claim the collection is temporary or not used in production if that is true and you will stop or change collection when testing ends. TesterFlow does not automatically delete data when a test ends. Keep the privacy policy, Play Store listing/Data safety disclosures, and app behavior consistent. This sample cannot guarantee Google Play approval; check current Play policies for your app and testing track.
Troubleshooting
- 401 Missing or invalid x-api-key header — the dashboard runs in production mode; pass TESTER_API_KEY.
- 400 testerId must be a valid UUID v4 — the stored ID is corrupt; clear app data or call eraseMyData().
- 400 device: Unrecognized key — the device object is strict; only send the 8 allowed keys.
- 429 Too many requests — 120 requests/minute per key; keep the heartbeat at 60s.
- Reinstall → new Tester ID. The ID lives in SharedPreferences; reinstalling or clearing app data creates a new ID.
- Cleartext HTTP blocked on Android 9+ use HTTPS or android:usesCleartextTraffic="true" for local development only.