PPactDocs
Mobile & SDKs

Pact SDK for Android / Kotlin

Pure-Kotlin client for Pact's consent-native CRM. Coroutines, kotlinx-serialization, white-label theming, offline writes.

place.pact:pact-sdk is the official Pact client for Kotlin and Android. It targets JVM 11 and uses Kotlin coroutines + kotlinx-serialization for I/O — no OkHttp / Retrofit / Ktor dependency to lock you into an HTTP stack.

Install

In your app or library build.gradle.kts:

kotlin
dependencies {
    implementation("place.pact:pact-sdk:0.1.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
}

Quickstart

kotlin
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import place.pact.sdk.PactClient

fun main() = runBlocking {
    val pact = PactClient(token = System.getenv("PACT_TOKEN"))
    pact.onCost = { cost ->
        println("cost: ${cost.actualCents}¢ (predicted=${cost.predictedCents})")
    }

    val list = pact.accounts.list(limit = 5)
    println(list.data.data)                  // typed `List<Account>`
    println("consent_filtered: ${list.consentFiltered}")

    val briefing = pact.agents.fire(
        agentId = "daily_briefing",
        input = JsonObject(mapOf("prompt" to JsonPrimitive("weekly review")))
    )
    println("status=${briefing.data.status} byok=${briefing.data.byok}")
}

The full sample lives in packages/sdk-android/example-quickstart — run with PACT_TOKEN=pact_test_… ./gradlew :example-quickstart:run.

In an Android Activity

kotlin
class MainActivity : AppCompatActivity() {
    private val pact by lazy {
        PactClient(token = BuildConfig.PACT_TOKEN, tenantId = BuildConfig.PACT_TENANT)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launch {
            val contacts = pact.contacts.list(limit = 25)
            renderContacts(contacts.data.data)
            if (contacts.consentFiltered == true) showConsentBadge()
        }
    }
}

Auth + tenancy

The same pact_live_* / pact_test_* keys (or OAuth access tokens) used by the JS and Swift SDKs work here.

kotlin
val pact = PactClient(token = "pact_live_…", tenantId = "tenant_uuid")
pact.token = newToken          // rotate at runtime
kotlin
val result = pact.agents.fire(agentId = "daily_briefing", input = JsonObject(emptyMap()))
when (result.data.status) {
    "consent_blocked" ->
        showConsentExplainer(result.data.consentBlockedSubjects.orEmpty())
    "ok" -> {
        val cost = result.cost
        if (cost?.charged == false) {
            // BYOK — tenant supplied the LLM credential, Pact charged 0.
        }
    }
}

Offline writes

kotlin
// Plug in a SharedPreferences-backed Storage to persist across launches:
class PrefsStorage(private val prefs: SharedPreferences) : OfflineQueue.Storage {
    override suspend fun load() = prefs.getString("pact.q", null)
        ?.let { Json.decodeFromString<List<QueuedWrite>>(it) } ?: emptyList()
    override suspend fun save(items: List<QueuedWrite>) {
        prefs.edit().putString("pact.q", Json.encodeToString(items)).apply()
    }
}

pact.setOnline(false)            // network observer drives this
try {
    pact.activities.log(
        subjectType = "contact", subjectId = id,
        kind = "call", occurredAt = Instant.now().toString()
    )
} catch (e: PactException.QueuedOffline) {
    // Mutation queued — surface "Saved offline" toast.
}
pact.setOnline(true)             // drains queue automatically

White-label theming

kotlin
val theme = resolveTokens(mapOf("colors" to mapOf("accentEmber" to tenant.brandColor)))
val emberInt = pactHexToColorInt(theme.colors.accentEmber)
button.setBackgroundColor(emberInt)

Compatibility notes

ConcernBehaviour
Kotlin / JVMBuilt against Kotlin 1.9.24, JVM 11.
CoroutinesPublic methods are suspend; the request pipeline runs on Dispatchers.IO.
Serializationkotlinx.serialization.json for the wire format.
ProGuard / R8No reflection used; the entity decoders are explicit pure functions.
Compose MultiplatformLibrary is pure-JVM with no Android dependencies; consume directly from commonMain.