Code Examples
Complete Kotlin and Java examples for all SDK methods
Complete, copy-pasteable examples for every public method of the Convert Android SDK. Kotlin is shown first; see Java Interop for the Java equivalents. All examples assume a single ConvertSDK instance built once in Application.onCreate() (see Initialization).
Build the SDK
import com.convert.sdk.android.ConvertSDK
import com.convert.sdk.core.model.LogLevel
val sdk = ConvertSDK.builder(applicationContext)
.sdkKey("YOUR_SDK_KEY")
.environment("prod")
.logLevel(LogLevel.INFO)
.build()Wait for readiness
sdk.onReady {
// Decisions are available from here on.
}Create a visitor context
// Auto-persisted UUID visitor.
val ctx = sdk.createContext()
// Explicit visitor id.
val ctx = sdk.createContext("visitor-42")
// Explicit visitor id with initial attributes.
val ctx = sdk.createContext(
visitorId = "visitor-42",
attributes = mapOf("plan" to "premium", "accountAgeDays" to 120),
)Run a single experience
val variation = ctx.runExperience("homepage-redesign")
when (variation?.key) {
"control" -> renderControl()
"treatment" -> renderTreatment()
null -> renderControl() // SDK not ready, visitor not bucketed, or experience absent
}Run all experiences for the visitor
val variations = ctx.runExperiences()
variations.forEach { applyVariation(it) }runExperiences() returns only the experiences the visitor is bucketed into; it filters out ones that failed audience/location rules.
Suppress the tracking event for a call
// Evaluate but do not report (e.g. consent-denied flow).
val variation = ctx.runExperience("homepage-redesign", enableTracking = false)
ctx.runExperiences(enableTracking = false)Bucketing, audience rules, and sticky persistence still run — the visitor is bucketed and the decision is stored. What is suppressed is both reporting paths: the outbound network event and the in-process bucketing event, so observers do not see a signal for a call you asked to be silent. See Tracking Control.
Preview a specific variation
setPreview forces one experience to resolve as a chosen variation on this context only — bypassing audiences, segments, locations, the environment check, experience and variation status, traffic allocation, any stored sticky decision, and the bucketing hash. That makes a draft or paused experience previewable exactly like a running one.
It is a suspend function, and awaiting it matters: when the previewed experience is not in the currently-loaded config, setPreview fetches it and does not return until the target has resolved. Await it, then render the screen that runs the experience.
import com.convert.sdk.core.preview.PreviewParam
// In the Activity/Fragment that receives the deep link.
val raw = intent.data?.getQueryParameter("convert_preview")
val parsed = raw?.let { PreviewParam.parse(it) } // null on a malformed value
if (parsed != null) {
lifecycleScope.launch {
val previewCtx = sdk.createContext()
previewCtx.setPreview(parsed.first, parsed.second) // (experienceId, variationId)
render(previewCtx.runExperience("homepage-redesign"))
}
}Note the ids are the numeric experience and variation ids from your dashboard, not the merchant-defined keys — runExperience still takes the key.
Three behaviors worth designing around:
- Zero trace, whole context. While a preview is active, that context writes nothing: no tracking events, no in-process
bucketing/conversionevents, no sticky decision, no goal-tracked mark, no segment persistence — for every experience and goal on it, not just the previewed one. Other experiences still decide normally so the screen renders coherently. A different, non-preview context on the sameConvertSDKbuckets, persists, and tracks as usual. - There is no "clear preview".
setPreviewis one-way for the lifetime of the context it was called on. To leave preview mode, stop using that context and go back to a normal one — which is why the snippet above builds a dedicatedpreviewCtxinstead of previewing the context the rest of the app uses. - Bad input is inert, not fatal. An unknown experience id, or a variation id that is not in the previewed experience, logs a WARN and leaves the context with no preview at all — it immediately resumes fully normal bucketing, tracking, and persistence.
runExperience(key) consults the preview override before anything else, so it returns the forced variation even for an experience absent from the loaded config. runExperiences() iterates the experiences in the loaded config, so an experience reachable only through the preview fetch is not part of that list — set a debugToken if you need draft and paused experiences to show up in the normal config fetch too. See the shared QA & Preview guide.
Evaluate a feature flag
val feature = ctx.runFeature("checkout-v2")
if (feature?.enabled == true) {
enableCheckoutV2()
}Read feature variables
import com.convert.sdk.android.getString
import com.convert.sdk.android.getInt
import com.convert.sdk.android.getDouble
import com.convert.sdk.android.getBoolean
val feature = ctx.runFeature("checkout-v2")
val color = feature?.getString("ctaColor") ?: "#0066ff"
val limit = feature?.getInt("maxItems") ?: 20
val price = feature?.getDouble("price") ?: 9.99
val experimental = feature?.getBoolean("experimental") == trueRun all features
val all = ctx.runFeatures() // every declared feature, evaluated for this visitorTrack a conversion
import com.convert.sdk.core.model.GoalData
import com.convert.sdk.core.model.GoalDataKey
import kotlinx.serialization.json.JsonPrimitive
// Bare goal.
ctx.trackConversion("signup-completed")
// With transactional goal data.
ctx.trackConversion(
goalKey = "purchase-completed",
goalData = listOf(
GoalData(GoalDataKey.AMOUNT, JsonPrimitive(49.99)),
GoalData(GoalDataKey.PRODUCTS_COUNT, JsonPrimitive(3)),
GoalData(GoalDataKey.TRANSACTION_ID, JsonPrimitive("tx-42")),
),
)Force a repeat transaction (bypass dedup)
By default the same goal records only once per visitor. For renewals or repeat purchases, bypass dedup:
ctx.trackConversion(
goalKey = "purchase-completed",
goalData = listOf(GoalData(GoalDataKey.AMOUNT, JsonPrimitive(49.99))),
conversionSetting = mapOf("forceMultipleTransactions" to true),
)On the repeat call the bare conversion is skipped (already recorded), the transaction payload is sent, and the conversion event fires. See Tracking Conversions.
Check whether a goal exists
if (ctx.hasGoal("purchase-completed")) {
// The goal key is declared in the current config.
}Set attributes (targeting input)
ctx.setAttributes(mapOf("plan" to "premium"))Attributes feed the audience/location rule engine and do not flow into outbound tracking events. setAttributes replaces, it does not merge — merge yourself with ctx.setAttributes(old + new) if needed.
Set location properties
ctx.setLocationProperties(mapOf("path" to "/checkout", "country" to "US"))Set segments (reported with events)
// Default segments — string values.
ctx.setDefaultSegments(mapOf("plan" to "premium", "country" to "US"))
// Custom segments — any JSON value.
ctx.setCustomSegments(mapOf(
"lifetimeValue" to 1250.75,
"isBetaTester" to true,
))Segments flow into outbound tracking events and feed audience rules. Both setters replace (not merge). Pass emptyMap() to clear on sign-out. See Segments.
Subscribe to and unsubscribe from events
import com.convert.sdk.android.EventCallback
import com.convert.sdk.core.event.SystemEvents
val token = sdk.on(SystemEvents.CONVERSION, EventCallback { data ->
// handle the conversion payload
})
sdk.off(SystemEvents.CONVERSION, token)Toggle tracking at runtime
sdk.setTrackingEnabled(false) // e.g. on consent withdrawal
sdk.setTrackingEnabled(true) // e.g. on consent re-grant
val enabled = sdk.isTrackingEnabled()See Tracking Control for the full consent matrix.
Next steps
- Return Types & Models — what each call returns
- Java Interop — the Java forms of these calls
- Offline Behavior — what happens to tracked events offline
Updated 28 days ago