Kotlin SDK

Configure the Voidhash Android SDK and look up its client options and methods.

The Kotlin SDK adds entitlements, feature flags, analytics, and observer-mode transaction reporting to native Android apps. Use this page to create the client, set its options, and find the methods you need. Each feature also has its own guide, linked from the section that introduces it.

The initial release is observer-only

SDK-started purchases and hosted paywalls are temporarily unavailable. Google Play transactions are still observed and submitted to Voidhash for revenue analytics, but the SDK never acknowledges or consumes them.

Compatibility

The SDK has the following requirements.

  • minSdk 23.
  • compileSdk 34.
  • AGP 8.9.0, Kotlin 2.0.21, and Java 8 bytecode.
  • Play Billing 8.0.0.

Two Gradle modules ship in @voidhash/android. :sdk (com.voidhash.sdk) is the public SDK you integrate against. :core (com.voidhash.core) is the shared engine it depends on.

Create the client

Create one client and keep it for the lifetime of the app.

import com.voidhash.sdk.Voidhash
import com.voidhash.sdk.VoidhashOptions

val voidhash = Voidhash.configure(
    context = applicationContext,
    publishableKey = "vh_pk_...",
)

The publishable key is safe to include in the app. Never ship vh_sk_... secret keys.

There is no schema argument, because the schema lives on the server and initialize() fetches it. initialize() connects to Google Play, resolves the schema, reconciles unfinished store transactions, and starts the analytics queue. Call it once the client exists.

lifecycleScope.launch {
    voidhash.initialize()
}

It is safe to call initialize() repeatedly. Only the first successful call does work, and concurrent callers wait for it. A failed call leaves the client uninitialized so you can retry.

Until initialization has succeeded, every method that needs the schema throws VoidhashException with the code CONFIGURATION_MISSING.

Client options

Pass a VoidhashOptions value to configure to change the defaults.

val voidhash = Voidhash.configure(
    context = this,
    publishableKey = "vh_pk_...",
    options = VoidhashOptions(debug = BuildConfig.DEBUG),
)

Each option is described below.

OptionDefaultDescription
baseUrlhttps://api.voidhash.comOverrides the API origin for self-hosted deployments.
ingestUrlSame origin as baseUrlOverrides only the analytics origin.
debugfalseEnables verbose logging and marks requests as debug-build traffic.
distinctIdGenerated anonymous IDSeeds the initial customer identity. Usually omit it and call identify().
enabledtrueSet it to false to ship the SDK fully inert.
readOnlytrueForced on while commerce features are unavailable.

Every method on VoidhashClient is a suspending function, except capture, getDistinctId, and setReadOnly. The SDK never blocks the caller's thread, and it never posts work back to the main thread on its own.

Disabled clients

With enabled: false, every method is inert. The SDK makes no requests and opens no billing connection. getProducts() returns an empty list, and getCurrentPerson() returns null.

Observer mode

The initial release always uses observer mode. Passing readOnly = false or calling setReadOnly(false) cannot transfer Google Play ownership to Voidhash yet.

Products and transaction reporting

Load the product catalog and restore the customer's existing purchases.

val products = voidhash.getProducts()
voidhash.restorePurchases()

Initialization and restore submit observed Google Play transactions to Voidhash without acknowledging or consuming them. purchase(...) remains in the SDK for a later commerce launch, but it currently raises READ_ONLY_PURCHASE_NOT_ALLOWED before it touches Play Billing.

After a successful host purchase, call reportTransaction(...) with its store transaction values. A valid report is persisted before it returns and delivered in the background, so a network or service outage delays reporting without failing the purchase flow. Use syncPurchases() for callbacks without transaction values and for restore recovery. Store observers and scans cannot recover a consumable already finished or consumed by the host. See Report and restore purchases for callback examples, error handling, and scan limits.

People and entitlements

Read the current person, identify them when they sign in, and reset the client when they sign out.

voidhash.getDistinctId()
voidhash.identify(externalUserId = "user-123", email = "a@b.co", name = "Ada")
voidhash.setPersonAttributes(mapOf("plan" to "pro"))

val person = voidhash.getCurrentPerson(forceFetch = true)
person?.activePerkIds

voidhash.reset() // Sign out: clears the local identity and cache

The SDK caches the person snapshot for two days. After five minutes it treats the snapshot as stale but still serves it from the cache. Pass getCurrentPerson(forceFetch = true) to bypass the cache. The SDK refreshes the snapshot on its own after purchases, restores, and identity changes.

See Check access and Identify customers for the underlying concepts.

Feature flags

Evaluate the flags you need by key.

val flags = voidhash.getFeatureFlags(listOf("new_onboarding"))
val enabled = flags.firstOrNull { it.key == "new_onboarding" }?.enabled == true

Pass an empty list to evaluate every flag. See Evaluate feature flags for variants and identity behavior.

Analytics

Capture an event, and call flush() when you need the queue sent right away.

voidhash.capture("checkout_started", mapOf("source" to "paywall"))
voidhash.flush()

The SDK batches events, sending a request after 20 events or every 5 seconds. Failed requests are retried with exponential backoff, so the wait grows after each failure, and the SDK honors the Retry-After header. Event names beginning with $ are reserved for Voidhash events. See Capture analytics.

Paywalls

Hosted paywalls are temporarily unavailable. presentPaywall(...) returns false, paywall resolution returns null, and the SDK performs no network or presentation work.

Shutdown

Call shutdown() when the process is going away for good.

voidhash.shutdown()

It flushes analytics and ends the Play Billing connection. Do not call it on every background event.