Android SDK Quickstart
The Transcend Headless SDK provides a headless consent management API for Android apps. The module is imported as io.transcend.sdk, and all API calls will go through the imported TranscendAPI.
Requirements: Android API 24+, Kotlin, JVM target 11
Add the dependency to your module-level build.gradle.kts:
dependencies {
implementation("io.transcend.sdk.headless:headless:0.9.0")
}Ensure mavenCentral() is in your repository list (no custom repository required):
repositories {
google()
mavenCentral()
}The library declares INTERNET and ACCESS_NETWORK_STATE permissions automatically.
Create a TranscendConfig and call TranscendAPI.init(). Register a ready callback **before** calling initialize, and it will fire once the SDK has loaded settings and (optionally) completed its initial sync.
import io.transcend.sdk.TranscendAPI
import io.transcend.sdk.models.TranscendConfig
val config = TranscendConfig(
bundleId = "YOUR_BUNDLE_ID", // Required — Transcend bundle ID
mobileAppId = "com.example.myapp", // Required — your app's mobile identifier pulled from https://app.transcend.io/consent-manager/native/applications?pageSize=10
isTest = false, // Whether to use the test or production deployment
cdn = "https://transcend-cdn.com", // Optional — defaults to Transcend's CDN
token = "AUTH_TOKEN", // Optional — auth token for backend sync. Configured using your preference store signing and encryption keys https://docs.transcend.io/docs/articles/preference-management/storing-consent-preferences#generate-a-user-token-on-your-backend-server
settingsOverrides = mapOf( // Optional — override remote settings at init
"country" to "EU",
"regime" to "GDPR",
),
)
TranscendAPI.ready { event ->
if (event.success) {
// SDK is ready — safe to call consent APIs
} else {
Log.e("Transcend", "Init failed: ${event.errorDetails}")
}
}
TranscendAPI.init(applicationContext, config)Use TranscendAPI.asyncInit() when you prefer non-throwing initialization. Errors are delivered via the ready callback instead of being thrown.
TranscendAPI.asyncInit(applicationContext, config)You can check for readiness in one of two ways: registering a callback that is invoked on ready, or through polling. Ready callback registration is shown in the Initialization section, and polling can be done with a periodic interval check calling the following function:
TranscendAPI.isReady() // returns BooleanNote: most API methods require the SDK to be ready and will throw TranscendError.NotInitialized otherwise.
If your initialization has encountered an error and needs re-initialization, you can tear down the API with destroy() and re-initialize. This should not be necessary during typical app usage, but may be helpful for debugging purposes.
TranscendAPI.destroy()Register listeners with addEventListener(type, callback). Each call returns a ListenerHandle, store it and pass it to removeEventListener() when done.
val consentHandle = TranscendAPI.addEventListener(EventListenerType.CONSENT_CHANGE) { event ->
val change = event as ConsentEvent.ConsentChange
Log.d("Transcend", "Consent changed: ${change.newConsent.purposes}")
}
val syncHandle = TranscendAPI.addEventListener(EventListenerType.SYNC) { event ->
val sync = event as ConsentEvent.Sync
Log.d("Transcend", "Sync completed")
}
// When no longer needed:
TranscendAPI.removeEventListener(consentHandle)
TranscendAPI.removeEventListener(syncHandle)| Listener type | ConsentEvent subclass | When it fires |
|---|---|---|
CONSENT_CHANGE | ConsentEvent.ConsentChange | Local consent state changes (e.g. after setConsent). |
SYNC | ConsentEvent.Sync | Backend sync completes and consent may have been updated. |
*(via ready)* | ConsentEvent.Ready | SDK initialization succeeds or fails. |
val consent = TranscendAPI.getConsent()
// consent.purposes — Map<String, PurposeValue>? (.True / .False / .Auto)
// consent.confirmed — Boolean
// consent.prompted — Boolean
// consent.timestamp — ISO 8601 string
// Boolean purposes map — consent.purposes?.mapValues { it.value.toBoolean() }, coalesces .Auto to truePass a map of purpose names to boolean values. Syncs to the backend by default.
val success = TranscendAPI.setConsent(
purposes = mapOf(
"Analytics" to true,
"Functional" to true,
"Advertising" to false,
),
autoSync = true, // Sync to backend after setting (default: true)
confirmed = true, // Mark consent as confirmed (default: true)
timestamp = null, // ISO timestamp; defaults to now
updated = true, // Mark consent as updated (default: true)
metadata = ConsentMetadata.Unchanged, // ConsentMetadata; see ConsentMetadata sealed class
metadataTimestamp = null,
)When autoSync is true, the SDK pushes consent to the backend using the token from TranscendConfig. Listen for SYNC events to react to remote updates.
If you need to test specific regimes, turn on/off sync, or force on any specific configuration values, the TranscendConfig object allows you to pass those through for a runtime override.
Pass overrides in TranscendConfig.settingsOverrides:
val config = TranscendConfig(
bundleId = "YOUR_BUNDLE_ID",
mobileAppId = "com.example.myapp",
settingsOverrides = mapOf(
"country" to "DE",
"countryRegion" to "EU",
"regime" to "GDPR",
),
)Some common override keys include: "partition", "country", "countryRegion", "regime", "defaultRegime".
**Backend sync requires an auth token.** Pass token when creating TranscendConfig at init — without it, sync is skipped and consent is not pushed to or pulled from the backend. You can generate an auth token by following the instructions here.
val config = TranscendConfig(
bundleId = "YOUR_BUNDLE_ID",
mobileAppId = "com.example.myapp",
token = "AUTH_TOKEN", // Required for sync
)
The SDK syncs consent with the Transcend backend automatically in two cases:
- During initialization, using the
tokenfrom theTranscendConfig. - After
setConsent, whenautoSyncis true (the default), using the sameTranscendConfigtokenwe initialized with.
There is no separate public sync() method. To trigger a sync, call setConsent with autoSync: true and ensure a valid token was provided at init.
To listen for sync completion:
TranscendAPI.addEventListener(EventListenerType.SYNC) { event ->
val sync = event as ConsentEvent.Sync
// Backend sync finished; sync.newConsent reflects any remote changes
}Sync is also skipped when SYNC_DISABLED is true in the loaded settings, or explicitly disabled using that field in an override.
Use these APIs to determine whether a third-party SDK (identified by a service ID) is allowed to run given the current consent state.
TranscendAPI.getSdkConsentStatus(
context,
"analytics_sdk",
object : TranscendCallback.ConsentStatusListener {
override fun onStatusReceived(status: ConsentStatus) {
when (status) {
ConsentStatus.ALLOW -> { /* SDK may run */ }
ConsentStatus.BLOCK -> { /* User denied required purpose(s) */ }
ConsentStatus.TCF_ALLOW -> { /* Allowed via TCF */ }
ConsentStatus.NO_SDK_FOUND -> { /* Service ID not in purpose map */ }
ConsentStatus.INTERNAL_ERROR -> { /* Error occurred */ }
}
}
},
)Pass specific service IDs, or `null`/empty to evaluate all known SDKs:
// Specific IDs
TranscendAPI.batchGetSDKConsentStatuses(
context,
arrayOf("analytics_sdk", "ads_sdk"),
object : TranscendCallback.ConsentStatusListListener {
override fun onStatusListReceived(statusMap: Map<String, ConsentStatus>) {
// e.g. {"analytics_sdk": ALLOW, "ads_sdk": BLOCK}
}
},
)
// All SDKs in the purpose map
TranscendAPI.batchGetSDKConsentStatuses(
context,
null,
object : TranscendCallback.ConsentStatusListListener {
override fun onStatusListReceived(statusMap: Map<String, ConsentStatus>) {
// statusMap contains every known service ID and its status
}
},
)Callback variant:
val callback = object : TranscendCallback.ConsentStatusListListener {
override fun onStatusListReceived(statusMap: Map<String, ConsentStatus>) {
for ((serviceId, status) in statusMap) {
Log.d("Transcend", "$serviceId: $status")
}
}
}
TranscendAPI.batchGetSDKConsentStatuses(context, arrayOf("analytics_sdk"), callback)| Status | Meaning |
|---|---|
ALLOW | All required purposes for this SDK are granted (or SDK is Essential). |
BLOCK | At least one required purpose was explicitly denied. |
TCF_ALLOW | SDK uses TCF and the TCF API is available. |
NO_SDK_FOUND | The service ID is not in the SDK purpose map. |
INTERNAL_ERROR | Returned if consent resolution encountered an error while reconciling the service ID's purposes. |
The SDK purpose map is loaded from settings and cached. Pass `cacheTTL` (milliseconds) to control how long the cache is reused before re-reading from settings.
Note: Callbacks are invoked on a background thread. You should marshal to the main thread before updating UI.
// Active privacy regimes (e.g. "GDPR")
val regimes = TranscendAPI.getRegimes()
// Purposes applicable under the current regime
val purposes = TranscendAPI.getRegimePurposes()
// Purpose display metadata (name, description, defaultConsent, essential, etc.)
val details = TranscendAPI.getPurposeDetails()
// Raw loaded settings
val settings = TranscendAPI.getLoadOptions()All throwing APIs can produce `TranscendError`:
| Error | Cause |
|---|---|
TranscendError.NotInitialized | init() has not been called. |
TranscendError.NotReady | SDK is still loading; wait for the ready callback. |
TranscendError.AlreadyInitialized | init() called more than once without destroy(). |
TranscendError.InvalidConfiguration(detail) | Invalid config (e.g. empty bundleId). |
TranscendError.InitFailed(detail) | Settings fetch or initialization failed. |