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.
Before you start
- Your app sets
minSdk23. - 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.
includeBuild("node_modules/@voidhash/android")Then depend on the module from your app.
dependencies {
implementation("com.voidhash.sdk")
}If you prefer to vendor the sources, include them directly instead.
include(":voidhash-core", ":voidhash-sdk")
project(":voidhash-core").projectDir = file("third_party/voidhash/core")
project(":voidhash-sdk").projectDir = file("third_party/voidhash/sdk")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 8.0.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.
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.
Configure one test offer in Studio
Set up the smallest catalog that can show a paywall and grant access.
- Create a perk such as
premium. - Create a product, choose its billing duration, and attach the perk.
- Create a paywall that includes the product, then publish it.
- Create a paywall location such as
onboardingand 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
Resolve the paywall assigned to a location and present it from an activity.
voidhash.presentPaywall(activity, location = "onboarding", listener = object : PaywallListener {
override fun onPurchaseCompleted(transaction: VoidhashTransaction) = unlockPremium()
override fun onEvent(name: String, properties: Map<String, Any?>) = track(name, properties)
override fun onDismiss() = Unit
})presentPaywall returns false when the backend has no published paywall for the location. Fall
back to your own screen instead of leaving the customer with nothing.
The SDK presents the paywall fullscreen and speaks the paywall bridge protocol natively, so the paywall's actions work without extra code. Purchases, restores, close, and external links are handled for you. Custom events and logs are forwarded to the listener.
Check access
Gate a feature on an active perk grant from the person snapshot.
val person = voidhash.getCurrentPerson()
val hasPremium = person?.activePerkIds?.contains("premium") == trueThe 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.