Closed Test SDK
Integrate the SDK into anyapp — the APK your testers install. The SDK sends activity to Dozenflow and registers your test channel on first launch. Organizers manage connections and proof in the Dozenflow app.
Library: com.groundspaceteam:closed-test-sdk · latest 0.2.21 · API: io.closedtest.sdk · Ingest: https://api.groundspaceteam.com
Quick start
- Add the Maven dependency to anyapp.
- Initialize with three required fields: owner email, Google Group join URL, and Play invite link.
- Ship a test build and open it once — the server receives
POST /v1/initand your test appears in Dozenflow.
dependencies {
implementation("com.groundspaceteam:closed-test-sdk:0.2.21")
}
ClosedTest.initialize(
context = applicationContext,
init = ClosedTestInit(
ownerEmail = "dev@example.com",
googleGroupUrl = "https://groups.google.com/g/your-closed-testers",
inviteLink = "https://play.google.com/apps/testing/com.example.app",
),
options = ClosedTestOptions(),
)
Call ClosedTest.initialize from Application.onCreate (or disable auto-init and call from your entry Activity). Current release: 0.2.21 on Maven Central. Release notes: SDK repository.
Required init fields
SDK ≥ 0.2.21 requires all three fields on every init path (code init or manifest auto-init). If any is missing or blank, auto-init is skipped and telemetry for mutual / connections will not start.
They are sent in POST /v1/init as owner_email, google_group_url, and invite_link. They are not copied into event payloads.
| Field | Where to get it | Why Dozenflow needs it |
|---|---|---|
Owner emailownerEmail / io.closedtest.sdk.owner_email |
The email of the person who owns this closed test — usually the same address you use to sign in to Dozenflow, or a dedicated inbox for the project (e.g. dev@yourstudio.com). |
Server-side account identity: match or create the organizer account, attach the test card, and route mutual invites / connections to the right person. In the mutual network the same email is also the tester identity after login. |
Google Group join URLgoogleGroupUrl / io.closedtest.sdk.google_group_url |
In Google Play Console: Testing → Closed testing (your track) → tester access uses a Google Group. Create or open the group in Google Groups and copy the public join link, e.g. https://groups.google.com/g/your-closed-testers.
|
Play closed tests often require testers to join a Google Group before they can opt in. Dozenflow stores this link on the test card and shows it in Connections so reciprocal testers know the exact join step. |
Play invite linkinviteLink / io.closedtest.sdk.invite_link |
In Google Play Console: Testing → Closed testing → open your track → copy the link to share with testers (opt-in URL). Format: https://play.google.com/apps/testing/<applicationId>.
|
How testers install your build from Play. Stored on the test card and reused in Connections / onboarding — no manual copy-paste from chat when someone accepts your mutual test. |
Tip: after the first successful init, open Dozenflow → your test → Connections to confirm the Group and invite links look correct. You can adjust them in the app if Play Console links change.
Manifest auto-init (optional)
Instead of calling ClosedTest.initialize in code, you can set the three required fields as meta-data. AndroidX App Startup runs when the dependency is on the classpath.
<application ...>
<meta-data
android:name="io.closedtest.sdk.owner_email"
android:value="dev@example.com" />
<meta-data
android:name="io.closedtest.sdk.google_group_url"
android:value="https://groups.google.com/g/your-closed-testers" />
<meta-data
android:name="io.closedtest.sdk.invite_link"
android:value="https://play.google.com/apps/testing/com.example.app" />
</application>
- Backend URL is fixed inside the SDK — do not configure it in app code.
- Missing or blank any of the three fields → auto-init is skipped.
- Duplicate
ClosedTest.initialize(...)calls are ignored.
If you only want code init (Quick start), disable auto-init:
<meta-data
android:name="io.closedtest.sdk.auto_init_enabled"
android:value="false" />
ClosedTestOptions
| Option | Purpose |
|---|---|
heartbeatIntervalMs | Foreground heartbeat interval |
backgroundSessionEndDelayMs | Delay before session_end after background |
collectInDebuggableBuilds | Collect telemetry in debug builds |
okHttpClient | Custom OkHttp client |
maxQueuedEvents | Room queue cap |
eventsBatchSize | Batch upload size |
uploadBackoffInitialMs / uploadBackoffMaxMs | Retry backoff |
proofFlowHintEnabled | Show “open organizer app” dialog after init (default true) |
proofFlowHintMaxShows | Max dialog shows per install (default 3) |
proofFlowHintCooldownMs | Cooldown after “Later” (default 7 days) |
proofFlowPackageNames | Packages checked for the installed Dozenflow app |
dailyReminderEnabled | Local daily notification when the app was not opened today (default true) |
dailyReminderHourLocal | Reminder hour, device timezone (default 15) |
dailyReminderMinuteLocal | Reminder minute (default 0) |
Manual events
ClosedTest.trackScreen("Home")
ClosedTest.trackInteraction("tap")
ClosedTest.trackEvent("onboarding_done", mapOf("step" to "2"))
ClosedTest.flush()
Navigation funnels: screen flow guide (screen_view).
Tester binding
ClosedTest.handleDeepLink(intent?.data)
ClosedTest.bindTester(
testerId = "…",
testSessionId = "…",
)
Organizer app hint dialog
When POST /v1/init returns proofflow_test_id and hints are enabled, the SDK may offer to open Dozenflow: proofflow://test/{proofflow_test_id}.
Local daily test reminder (not FCM)
Local notification on the tester device — no Firebase in anyapp. Default 15:00 local time if the app was not opened yet today. Requires POST_NOTIFICATIONS on Android 13+.
Discovery marker
Exported ContentProvider at {applicationId}.closedtest.discovery so Dozenflow can verify the SDK is installed in a specific package.
What runs automatically
POST /v1/initwith owner email + Group + invite on first launch- Lifecycle:
session_start,session_end, foreground/background - Foreground heartbeat, Room queue, batched upload
- Token refresh on HTTP 401
After SDK init
- Install Dozenflow and sign in with the same owner email (or link the account).
- Open your test — it should appear after the first successful SDK init from anyapp.
- Check Connections: Group join URL and Play invite link from init should match what testers need.
- Distribute the build; sessions and campaign signals appear when testers open anyapp.
Troubleshooting
| Symptom | Check |
|---|---|
| SDK never starts / no init | All three required fields set and non-blank; check Logcat for auto-init skip; try explicit ClosedTestInit |
| Test missing in Dozenflow | Successful POST /v1/init; owner email matches signed-in account; anyapp opened at least once on a device |
| Wrong Group or invite in Connections | Update values in Play Console, ship new build with corrected init, or edit on test card in Dozenflow if shown |
| No events on server | Network; 403/401 on init; wrong package on the test card; anyapp never opened after shipping the SDK |