Kotlin quickstart

Add Voidhash to a native Android app and show your first paywall.

This guide adds Voidhash to an existing Android app. By the end you will have the SDK installed, a paywall on screen, and an access check that gates a premium feature.

Purchases arrive in a later release

In the initial release, SDK-started purchases are unavailable: a purchase started in a paywall shows an error. The install, configuration, paywall and access-check steps apply today.

Before you start

  • Your app sets minSdk 23.
  • You build with AGP 8.9.0.
  • You build with Kotlin 2.0.21.

Install the SDK

Add the SDK to your Gradle build. The SDK ships as a Gradle module inside the npm package @voidhash/android, so point your build at the installed package directory.

settings.gradle.kts
includeBuild("node_modules/@voidhash/android")

Then depend on the module from your app. Gradle takes it from the included build, so the dependency needs no version and is never downloaded from a repository.

app/build.gradle.kts
dependencies {
    implementation("com.voidhash:sdk")
}

If you prefer to vendor the sources, copy the package's core and sdk directories into your project and include them directly instead.

settings.gradle.kts
include(":voidhash-core", ":voidhash-sdk")
project(":voidhash-core").projectDir = file("third_party/voidhash/core")
project(":voidhash-sdk").projectDir = file("third_party/voidhash/sdk")

Then tell the SDK module where the core module lives by adding voidhash.coreProjectPath=:voidhash-core to your gradle.properties, and depend on project(":voidhash-sdk") from your app.

Add the billing permission to your manifest. The SDK contributes INTERNET through its own manifest, so you do not need to add that one.

<uses-permission android:name="com.android.vending.BILLING" />

Make sure Play Billing 9.1.0, Play Services Base, OkHttp 4.x, and kotlinx-coroutines are on the runtime classpath.

Configure the client

Create the client in your Application class with your project's publishable key.

App.kt
import com.voidhash.sdk.Voidhash
import com.voidhash.sdk.VoidhashOptions
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope

class App : Application() {
    override fun onCreate() {
        super.onCreate()

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

        ProcessLifecycleOwner.get().lifecycleScope.launch {
            voidhash.initialize()
        }
    }
}

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

configure is synchronous and cheap. initialize() does the real work: it connects to Google Play, resolves the project schema, and reconciles anything the store still reports as unfinished. You can call initialize() repeatedly. Only the first successful call does work, and a failed call can be retried. The client is also reachable as Voidhash.shared.

Application.onCreate runs in every process of your app, including a service declared with its own android:process. Voidhash runs only in the main process: anywhere else configure returns an inert client that makes no requests and leaves the SDK's stored data alone, so the code above is safe as it is.

Configure one test offer in Studio

Set up the smallest catalog that can show a paywall and grant access.

  1. Create a perk such as premium.
  2. Create a product, choose its billing duration, and attach the perk.
  3. Create a paywall that includes the product, then publish it.
  4. Create a paywall location such as onboarding and assign the published paywall.

Connect Google Play Console before you test a release build. See Store setup for the steps. For the model behind the catalog, see Products and perks and Paywalls.

Present the paywall

Add the ui module (implementation("com.voidhash:ui")) and pass VoidhashUiPaywallRenderer() as paywallRenderer when you configure the SDK. Then present the flow assigned to a location from an activity:

when (voidhash.presentFlow(activity, "onboarding")) {
    is FlowResult.Purchased, FlowResult.Restored -> unlockPremium()
    is FlowResult.Failed -> showOwnUpgradeScreen()
    else -> Unit
}

presentFlow returns once the paywall is gone. It reports Failed with NOT_ASSIGNED when no paywall is published for the location; fall back to your own screen instead of leaving the customer with nothing. Purchases, restores, close, and links inside the paywall are handled for you. See Display a paywall for every result and the capabilities a paywall can use.

Check access

Gate a feature on an active perk grant from the person snapshot.

val person = voidhash.getCurrentPerson()

val hasPremium = person?.hasActivePerk("premium") == true

The snapshot refreshes after a successful purchase or restore. See Check access for caching behavior and failure handling.

Run a test purchase

Build and run the app, then buy through the presented paywall. Use a device signed into an account in a Play testing track.

Purchases sync to the server first. The SDK acknowledges the purchase only after validation succeeds. Consumables are consumed instead of acknowledged.

Next steps