At a glance
-
Kotlin library, callable from Java. Package
com.jointestbuddy, version 0.2.0. - minSdk 24.
- Starts on its own from a key in your manifest: no code, the same steps for Kotlin, Java, Flutter, React Native, Expo, Unity and Capacitor.
-
One library dependency: Google's Play Install Referrer client
(
com.android.installreferrer:installreferrer:2.2). No AndroidX, no WorkManager. Like any Kotlin library, it also brings the Kotlin standard library. -
Distributed through JitPack:
com.github.jointestbuddy:testbuddy-android:0.2.0. -
Adds the
INTERNETpermission and one invisible activity that handles TestBuddy links. - No user interface. A tester who taps "Link it" sees a short toast; nobody else sees anything.
What it sends, and what it does not
What it sends
- At each launch, from every phone: your app key and your app's package name. This hello tells your TestBuddy dashboard that the build carries the SDK.
- Once per install, to link it: the 6-character place code from the install referrer or the "Link it" link, with the app key and package name. TestBuddy answers with an install token, which the SDK keeps.
- After linking: sessions (when the app came to the foreground and how many seconds it stayed there, capped at 4 hours per session) and the objective keys you record. Each event carries a random event id so it is never counted twice.
What it does not collect
No name, email, advertising ID, device identifier, location, contacts, screen content or crash data. It does not see which screens are opened or what is typed. It reads the install referrer only to find a TestBuddy code in it.
On your real users' phones
A phone that is not linked to a tester sends the hello and nothing else. Sessions are still counted locally, because a tester's sessions before the link count too, but they stay in the app's private storage (at most 200 events) and are never sent unless that install gets linked.
Install
The SDK starts on its own when your app starts: it reads your key from
the manifest, so no step is code. Whatever your app is built with, it
comes down to the dependency and one manifest line. Use the key shown
in TestBuddy at step 3 of the setup (later in Your app, under Setup
details); it starts with pk_. The setup shows these
steps with your key already in them.
Kotlin or Java (Android Studio)
In settings.gradle.kts, inside dependencyResolutionManagement { repositories { } }:
maven("https://jitpack.io")
In app/build.gradle.kts:
dependencies {
implementation("com.github.jointestbuddy:testbuddy-android:0.2.0")
}
In app/src/main/AndroidManifest.xml, inside <application>:
<meta-data android:name="com.jointestbuddy.KEY" android:value="pk_..." />
Flutter
In android/build.gradle.kts, inside allprojects { repositories { } } (Groovy: maven { url "https://jitpack.io" } in android/build.gradle):
maven("https://jitpack.io")
At the end of android/app/build.gradle.kts:
dependencies {
implementation("com.github.jointestbuddy:testbuddy-android:0.2.0")
}
In android/app/src/main/AndroidManifest.xml, inside <application>:
<meta-data android:name="com.jointestbuddy.KEY" android:value="pk_..." />
React Native
React Native already adds the JitPack repository. In
android/app/build.gradle, inside dependencies { }:
implementation("com.github.jointestbuddy:testbuddy-android:0.2.0")
In android/app/src/main/AndroidManifest.xml, inside <application>:
<meta-data android:name="com.jointestbuddy.KEY" android:value="pk_..." />
Expo
Expo regenerates android/ at each prebuild, so a config
plugin adds the SDK and your key every time. Save it as
plugins/withTestBuddy.js at the root of your project:
// Adds the TestBuddy SDK to the Android build at each prebuild: the dependency and your key.
const { AndroidConfig, withAndroidManifest, withAppBuildGradle, withProjectBuildGradle } = require('expo/config-plugins')
module.exports = function withTestBuddy(config, { key }) {
config = withProjectBuildGradle(config, (c) => {
if (!c.modResults.contents.includes('jitpack.io'))
c.modResults.contents = c.modResults.contents.replace(/allprojects\s*\{\s*repositories\s*\{/, (m) => `${m}\n maven { url 'https://www.jitpack.io' }`)
return c
})
config = withAppBuildGradle(config, (c) => {
if (!c.modResults.contents.includes('testbuddy-android'))
c.modResults.contents = c.modResults.contents.replace(/dependencies\s*\{/, (m) => `${m}\n implementation("com.github.jointestbuddy:testbuddy-android:0.2.0")`)
return c
})
return withAndroidManifest(config, (c) => {
const app = AndroidConfig.Manifest.getMainApplicationOrThrow(c.modResults)
AndroidConfig.Manifest.addMetaDataItemToMainApplication(app, 'com.jointestbuddy.KEY', key)
return c
})
}
Then, in app.json, add it to expo.plugins:
["./plugins/withTestBuddy", { "key": "pk_..." }]
Build with eas build --platform android, or run
npx expo prebuild again with a local
android/ folder. Expo Go cannot load native code; the
build you upload to Play does.
Unity
In Project Settings, Player, Android, Publishing Settings, Build, turn
on Custom Main Manifest, Custom Main Gradle Template and Custom Gradle
Settings Template. Then, in
Assets/Plugins/Android/settingsTemplate.gradle, inside
dependencyResolutionManagement { repositories { } }:
maven { url "https://jitpack.io" }
In Assets/Plugins/Android/mainTemplate.gradle, inside dependencies { }:
implementation "com.github.jointestbuddy:testbuddy-android:0.2.0"
In Assets/Plugins/Android/AndroidManifest.xml, inside <application>:
<meta-data android:name="com.jointestbuddy.KEY" android:value="pk_..." />
Capacitor or Ionic
In android/build.gradle, inside allprojects { repositories { } }:
maven { url "https://jitpack.io" }
In android/app/build.gradle, inside dependencies { }:
implementation "com.github.jointestbuddy:testbuddy-android:0.2.0"
In android/app/src/main/AndroidManifest.xml, inside <application>:
<meta-data android:name="com.jointestbuddy.KEY" android:value="pk_..." />
Then: upload the build and open it once
Upload the build to your closed test track and open it once on any phone. The SDK says hello, the setup step turns verified, and you can join the next wave. The closed testing setup guide covers the Play Console side.
Prefer code?
Instead of the manifest line, call TestBuddy.init once in
Application.onCreate. It is ignored if the SDK already
started from the manifest. Its optional third parameter,
apiUrl, is for local testing only.
import android.app.Application
import com.jointestbuddy.TestBuddy
class App : Application() {
override fun onCreate() {
super.onCreate()
TestBuddy.init(this, "pk_...")
}
}
Java
Every public call is @JvmStatic, so Java calls it
directly on TestBuddy:
import android.app.Application;
import com.jointestbuddy.TestBuddy;
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
TestBuddy.init(this, "pk_...");
}
}
TestBuddy.objective("first_entry");
TestBuddy.link(code);
boolean linked = TestBuddy.isLinked();
long seconds = TestBuddy.getForegroundSeconds();
The callback of link is a Kotlin function type, so from
Java it returns Unit.INSTANCE (import kotlin.Unit;):
TestBuddy.link(code, linked -> {
// Called on a background thread.
return Unit.INSTANCE;
});
Objectives
An objective is a step you want testers to reach, like saving a first entry. An app has up to 3, each with a key of lowercase letters, digits and underscores (up to 40 characters), set on your app in TestBuddy. Record one where it happens in your code:
// After the first entry is saved.
TestBuddy.objective("first_entry")
Your dashboard shows how many testers reached each objective. Keys
that are not on your app are ignored. Calling
objective before the SDK starts does nothing.
Linking an install to a tester
Automatic: the install referrer
TestBuddy gives each tester a Play Store install link for your app
with a referrer:
https://play.google.com/store/apps/details?id=<package>&referrer=tb_<code>. On the first launch, the SDK reads the referrer once through the
Play Install Referrer library, finds the tb_ code and links the install. If the
network fails, it tries again at the next launch. An install from
anywhere else carries Play's default referrer, with no code.
One tap: the testbuddy:// link
The SDK's manifest declares LinkActivity, which opens on:
testbuddy://<package>/link?code=XXXXXX
The TestBuddy web shows a "Link it" button on Android that opens this
link. The activity is translucent and kept out of recents: it links
the install, shows "Linked to TestBuddy" (or "This TestBuddy link did
not work"), then opens your app's launch activity. Without a valid
code, or in an app that never called init, it closes at
once. You add nothing to your manifest: the activity is merged from
the library, with your applicationId as the link's host.
A custom flow: TestBuddy.link(code)
If you want your own way to enter the code, call
link with the 6 letters and digits the tester sees in
TestBuddy:
TestBuddy.link(code) { linked ->
// Background thread: switch to the main thread before touching views.
runOnUiThread { showLinked(linked) }
}
linked is true once the install is linked; it is false
for an unknown code or when init was not called. Keep
such a screen out of sight, in a debug or settings menu for example: a
code prompt in your app would also show to users who are not testers,
and to Google's reviewers. That is why the SDK uses a link instead.
Checking the state
TestBuddy.isLinked is true once the install is linked.
TestBuddy.foregroundSeconds is the time since the app
last came to the foreground, 0 in the background. Both are handy on a
debug screen.
Sessions, offline queue and retries
- Sessions are counted with activity lifecycle callbacks: a session starts when the first activity starts and ends when the last one stops. The SDK queues it at that moment.
-
The queue lives in the app's SharedPreferences
(file
com.jointestbuddy) and keeps the latest 200 events. Events wait there until the install is linked and the network answers. - Sending happens in batches of 100 when the app comes to the foreground and when a session ends. A failed send is retried at the next launch or session end. There is no background job, so nothing leaves while the app stays closed.
- Late events still count: TestBuddy accepts a session up to 48 hours after it happened, and assigns it to the tester's test day in their time zone.
- Errors: a rate limit, a server error or no network keeps the events for the next try; a batch refused for good is dropped so it cannot block the queue. When a place ends, the SDK forgets its token and its queue.
Threading and safety
- Network calls and queue writes run on one background thread, so they never race and never block your main thread.
- Every task the SDK runs catches its own failures: an uncaught exception would kill your app, so the SDK swallows its errors instead of throwing them.
- Network calls time out after 10 seconds and go over HTTPS to jointestbuddy.com.
-
Calls made before
initdo nothing, andlinkreports false. - No AndroidX and no WorkManager: no version conflict with your own dependencies.
Play Data safety declaration
Closed testing tracks need a Data safety form; only apps active exclusively on internal testing are exempt. Google also states that data sent off the device by SDKs in your app counts as your app's collection, and that you alone are responsible for complete and accurate declarations (Provide information for Google Play's Data safety section).
What the SDK transmits, to help you fill it in:
- Session start times and foreground durations, and objective keys: this looks like what Play calls "App activity", "App interactions" (information about how a user interacts with the app).
- An install token that TestBuddy issues for a linked install: check whether it falls under Play's "Device or other IDs" (identifiers that relate to an individual device, browser or app).
- The app key and package name in the hello: they describe your app, not the user.
- All of it is encrypted in transit (HTTPS). TestBuddy deletes session events 120 days after receiving them (privacy).
This is guidance, not legal advice. Check Play's Data safety form and its definitions for your own app, and review your answers when you remove the SDK.
Removing it before production
Keep the SDK in your closed test build for the length of your wave: the terms ask for it, and your testers' days depend on it. Once your wave is over, remove it before your production release:
-
Delete the
implementation("com.github.jointestbuddy:testbuddy-android:0.2.0")line, or the TestBuddy plugin entry in an Expo app. -
Delete the
com.jointestbuddy.KEYmanifest line (or theTestBuddy.initline) and anyobjectiveorlinkcalls. - Remove the JitPack repository if nothing else uses it.
The link activity and the INTERNET permission go away
with the dependency, unless your app declares that permission itself.
Requirements
- Android 7.0 (API 24) or later.
- Your app described in TestBuddy, and its
pk_key. - Your app published on a closed testing track on Google Play.