Return Types & Models

Kotlin data classes, enums, and feature-variable accessors

This page documents the public Kotlin types the SDK returns and the enums you pass in. All model types live in com.convert.sdk.core.model; the event constants live in com.convert.sdk.core.event. The SDK never throws from its public decision methods — absent or not-ready data surfaces as null or an empty list.

Variation

Returned by ConvertContext.runExperience(...) (nullable) and runExperiences(...) (as a list). A Kotlin data class with all-nullable fields:

public data class Variation(
    val id: String? = null,
    val key: String? = null,
    val name: String? = null,
    val experienceId: String? = null,
    val experienceKey: String? = null,
    val experienceName: String? = null,
    val bucketingAllocation: Double? = null,
    val changes: List<JsonElement>? = null,
)
  • key — the merchant-defined variation key from the Convert dashboard; this is what you branch on.
  • id — the variation identifier.
  • bucketingAllocation — the basis-point allocation the visitor hit. Populated on fresh bucketing; null on a sticky lookup.
  • changes — raw variation changes, as JsonElement values.
val variation = ctx.runExperience("homepage-redesign")
when (variation?.key) {
    "control" -> renderControl()
    "treatment" -> renderTreatment()
    null -> renderControl() // not ready / not bucketed / experience absent
}

Feature and FeatureStatus

Returned by ConvertContext.runFeature(...) (nullable) and runFeatures(...) (as a list):

public data class Feature(
    val id: String? = null,
    val key: String? = null,
    val name: String? = null,
    val status: FeatureStatus,
    val variables: Map<String, JsonElement>? = null,
    val experienceId: String? = null,
    val experienceKey: String? = null,
    val experienceName: String? = null,
) {
    val enabled: Boolean get() = status == FeatureStatus.ENABLED
}

public enum class FeatureStatus { ENABLED, DISABLED }
  • enabled is a convenience for status == FeatureStatus.ENABLED.
  • When a feature is DISABLED, variables is null and the accessors below return null.
val feature = ctx.runFeature("checkout-v2")
if (feature?.enabled == true) {
    enableCheckoutV2()
}

Feature variable accessors

When a feature is ENABLED, variables holds JsonElement values — usually JsonPrimitive scalars. For the common scalar case, use the extension helpers in com.convert.sdk.android (file facade FeatureExtensions):

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 color = feature?.getString("ctaColor") ?: "#0066ff"
val limit = feature?.getInt("maxItems") ?: 20
val price = feature?.getDouble("price") ?: 9.99
val experimental = feature?.getBoolean("experimental") == true

Coercion is strict-then-loose: numeric primitives resolve directly; numeric strings fall back through toXxxOrNull(). JsonNull, arrays, nested objects, and absent keys all return null. For structured (JsonObject / JsonArray) variables, read feature.variables?.get("key") and decode the JsonElement yourself with kotlinx.serialization.

GoalData and GoalDataKey

Passed to ConvertContext.trackConversion(...) to attach transactional data to a conversion:

public data class GoalData(
    val key: GoalDataKey? = null,
    val value: JsonElement? = null,
)

GoalDataKey values and their JSON names on the wire:

Enum valueJSON nameTypical value
AMOUNTamountnumber
PRODUCTS_COUNTproductsCountinteger
TRANSACTION_IDtransactionIdstring
CUSTOM_DIMENSION_1customDimension1string / number
CUSTOM_DIMENSION_2customDimension2string / number
CUSTOM_DIMENSION_3customDimension3string / number
CUSTOM_DIMENSION_4customDimension4string / number
CUSTOM_DIMENSION_5customDimension5string / number
import com.convert.sdk.core.model.GoalData
import com.convert.sdk.core.model.GoalDataKey
import kotlinx.serialization.json.JsonPrimitive

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")),
    ),
)

LogLevel

public enum class LogLevel { ERROR, WARN, INFO, DEBUG, TRACE, SILENT }

Passed to Builder.logLevel(...) as an inclusive floor — INFO emits error/warn/info and drops debug; SILENT drops everything. The default is DEBUG, so set an explicit level for production builds. See Configuration Options.

PreviewParam

The one preview type your app calls directly. It parses the value of a convert_preview deep-link query parameter into the (experienceId, variationId) pair that ConvertContext.setPreview(...) takes:

import com.convert.sdk.core.preview.PreviewParam

val parsed: Pair<String, String>? = PreviewParam.parse("12345.67890")
// parsed?.first  == "12345"  (experienceId)
// parsed?.second == "67890"  (variationId)

Both segments must be non-empty and numeric-only, split on a single dot. Anything else — a missing dot, an extra dot, an empty or non-numeric segment — returns null. It is a pure function: it never throws, logs, or performs I/O, so guarding on null and falling through to normal behavior is the whole error path. parse is @JvmStatic, so Java callers reach it as PreviewParam.parse(raw).

The forced decision itself is produced internally by PreviewDecision, which returns the same Variation shape as a normal decision with bucketingAllocation = null (no hash was computed). You do not call it — runExperience consults it for you once a preview is set. See the shared QA & Preview guide and Code Examples.

SystemEvents

Well-known event names you can subscribe to with sdk.on(event, callback). Each is a String constant on com.convert.sdk.core.event.SystemEvents:

ConstantEvent name
READYready
CONFIG_UPDATEDconfig.updated
API_QUEUE_RELEASEDapi.queue.released
BUCKETINGbucketing
CONVERSIONconversion
SEGMENTSsegments
LOCATION_ACTIVATEDlocation.activated
LOCATION_DEACTIVATEDlocation.deactivated
AUDIENCESaudiences
DATA_STORE_QUEUE_RELEASEDdatastore.queue.released

The shared Event System doc explains when each fires.

EventCallback and SubscriptionToken

public fun interface EventCallback {
    public fun onEvent(data: Map<String, Any?>)
}

EventCallback is a Kotlin fun interface, so Kotlin and Java lambdas both convert to it. sdk.on(event, callback) returns an opaque SubscriptionToken; pass it to sdk.off(event, token) to unsubscribe. See Initialization and Java Interop.

Next steps


Did this page help you?