Technical guide · Android / Kotlin
Integrate the Tester Marketplace Android SDK in 30 lines
Updated:
Without this step testers can install your app but we cannot measure whether they use it, and you pay without evidence. The full integration is two copied files and a 60-line Activity.
How it works
- The tester joins your campaign in their web panel and taps “Generate code”: they get an 8-character code (ABCD-EFGH) valid for 15 minutes and a single use, or tap “Open in the app” to hand it to your app without typing.
- Your app redeems the code at POST /track/pair and receives a 90-day token that can only send sessions and events for that assignment. No login screen and no Tester Marketplace passwords inside your app.
- Each time the app comes to the foreground you send session/start and, on leaving, session/end. The panel then shows sessions, minutes and active days per tester, and payouts are released according to the campaign rules.
Steps
Copy Tracker.kt and AssignmentProgress.kt from the SDK into your project (no external dependencies: HttpURLConnection and coroutines) and follow the three steps.
1. Manifest: accept the deep link
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="testermarketplace" android:host="pair" />
</intent-filter>
</activity>With this intent-filter, the “Open in the app” button on the assignment opens your app with the code preloaded. If the tester is on another device they can always type it: keep the text field.
2. Activity: pair and record sessions
class MainActivity : AppCompatActivity() {
private var sessionStartedAt = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Tracker.apiBaseUrl = "https://testermarketplace.com/api-proxy"
Tracker.deviceId = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
val prefs = getSharedPreferences("tm", MODE_PRIVATE)
Tracker.authToken = prefs.getString("tm_token", "") ?: ""
Tracker.assignmentId = prefs.getString("tm_assignment", "") ?: ""
findViewById<Button>(R.id.pairButton).setOnClickListener {
pair(findViewById<EditText>(R.id.code).text.toString())
}
Tracker.pairingCodeFromUri(intent?.dataString)?.let { pair(it) } // deep link
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Tracker.pairingCodeFromUri(intent.dataString)?.let { pair(it) }
}
private fun pair(code: String) = Tracker.pair(code) { result ->
result.onSuccess { r ->
getSharedPreferences("tm", MODE_PRIVATE).edit()
.putString("tm_token", r.token).putString("tm_assignment", r.assignmentId).apply()
Tracker.startSession(); sessionStartedAt = System.currentTimeMillis()
}
}
override fun onResume() {
super.onResume()
if (Tracker.authToken.isNotBlank() && sessionStartedAt == 0L) {
Tracker.startSession(); sessionStartedAt = System.currentTimeMillis()
}
}
override fun onPause() {
super.onPause()
if (Tracker.authToken.isNotBlank() && sessionStartedAt > 0L) {
Tracker.endSession(((System.currentTimeMillis() - sessionStartedAt) / 1000).toInt())
sessionStartedAt = 0L
}
}
}Replace SharedPreferences with EncryptedSharedPreferences in production and use your own stable deviceId (persisted UUID). Everything else is literal.
3. Integration test (10 minutes)
- Create a validation campaign with 1 tester and 1 day using the same packageName as the real one.
- Join with a tester account of your own, generate the code and pair your test build.
- Open and close the app: within a minute the “Evidence” column in the panel shows the session and the activation screen stops warning in red.
- Activate the real campaign. If you activate without a single session received, we ask you to acknowledge the risk in writing: testers could use the app with no record, and you would still have to pay.
In-app events and Play Integrity (optional)
If your campaign requires specific events (level completed, test purchase…), send them with Tracker.trackEvent("exact_name") using the same strings you entered when creating the campaign. If you enabled Play Integrity, obtain the token with Google's API and upload it with Tracker.submitPlayIntegrityToken(): it is the only proof that the app ran on a real phone, and that tester shows as “Play Integrity ✓” in the panel.
Frequently asked questions
Does the tester need to log in to my app?
No. The pairing code replaces the login: your app never sees the tester's email or password, and the token it receives grants no access to anything else in their account.
What if the tester reinstalls the app or switches phones?
They generate another code from their assignment and pair again. The previous token stays valid for 90 days, but each assignment shows a single paired device in the panel.
Can I integrate it in a hybrid app (Flutter, React Native, Unity)?
Yes. It is three JSON HTTP calls (pair, session/start, session/end) with an Authorization: Bearer <token> header. The full OpenAPI reference is at /docs.
How do I know a tester actually used the app rather than just installing it?
In the campaign panel each tester shows sessions, minutes, active days and the “Evidence” column (Play Integrity ✓ or app-reported only). Before each payout tranche is released you also receive an email summary and have 48 hours to open a dispute.