Display a paywall

Present the flow assigned to a location and react to how it ended.

Use this page to show paywalls from the Kotlin SDK. A flow is the paywall, or another sequence of screens such as an onboarding, that you design and publish in Studio. A location is the stable slug your app asks for, such as onboarding or settings-upsell; the SDK calls it the placement. You assign flows to locations in Studio, as described in Paywall locations.

Purchases arrive in a later release

In the initial release, SDK-started purchases are unavailable: a purchase started in a flow shows an error inside the flow, which stays open. Everything else on this page applies today.

Add the renderer

Flows are drawn natively by the SDK's optional ui module. Add it next to the SDK:

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

Pass its renderer when you configure the SDK, together with the capabilities your flows may use:

val voidhash = Voidhash.configure(
    context,
    "vh_pk_…",
    VoidhashOptions(
        paywallRenderer = VoidhashUiPaywallRenderer(),
        flowCapabilities = setOf(FlowCapability.LINKS, FlowCapability.ATTRIBUTES),
    ),
)

Without a renderer, no flow is shown and presentFlow reports RENDERER_MISSING. The ui module ships native code for arm64-v8a and x86_64 devices.

Present it

Ask for a location, and the SDK presents the flow assigned to it fullscreen above the activity you pass. presentFlow suspends until the flow is gone and returns how it ended. It never throws for a flow that cannot be shown; that is a result too.

when (val result = voidhash.presentFlow(activity, "settings-upsell")) {
    is FlowResult.Purchased -> unlockPro()
    FlowResult.Restored -> refreshAccess()
    is FlowResult.Finished -> saveAnswers(result.result, result.variables)
    FlowResult.Closed -> Unit
    is FlowResult.Failed -> showOwnUpgradeScreen()
}
ResultWhen
PurchasedThe customer bought a product in the flow. productId is the store product ID
RestoredThe customer restored their purchases from the flow
FinishedThe flow reached one of its finish actions. result names it; variables holds the answers
ClosedThe flow's close action, the back button, dismissFlow(), or another flow replacing it
FailedThe flow could not be shown, or stopped while on screen; see below

Purchases and restores started in a flow go through the same purchase pipeline as any other purchase. A successful one ends the flow. A failed or cancelled one shows its error inside the flow and leaves it open, so the customer can try again.

Call dismissFlow() to close the flow on screen. Only one flow is shown at a time: presenting another closes the current one. Cancelling the coroutine that called presentFlow also closes its flow.

Handle failures

FlowResult.Failed carries a FlowException whose reason tells you why:

ReasonMeaning
NOT_ASSIGNEDNo flow is assigned to the location for this customer
UNAVAILABLENothing is cached and Voidhash is unreachable, or the flow could not download
RENDERER_MISSINGpaywallRenderer is not set
UNSUPPORTED_PACKAGEThe flow was published for a newer SDK; update the SDK
PRESENTATION_FAILEDThe flow could not be shown, or stopped while on screen
DISABLEDThe client is disabled or was shut down

When you clear or archive a location in Studio, later presentations report NOT_ASSIGNED. Keep a fallback for important entry points so the customer still has a way to upgrade.

Grant capabilities

Anything a flow does beyond showing screens, purchasing and restoring needs a capability you grant in flowCapabilities. An action needing a capability you did not grant is skipped.

CapabilityLets a flow
LINKSOpen links in the browser or the app that handles them. Granted by default
ATTRIBUTESSet attributes on the current person, like setPersonAttributes
NOTIFICATIONSAsk the customer for permission to send notifications
TRACKINGAsk for tracking permission. Android has no such prompt, so the request answers false

On Android 13 and later, a notification request shows the system prompt. Declare the permission in your app's manifest; without it the request is refused and the flow is told notifications are not allowed:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

On earlier Android versions there is no prompt, and the flow learns whether notifications are enabled for your app.

Saved progress

As a customer moves through a flow, its variables, such as the answers given so far, are saved on the device. Presenting the same paywall again picks up where they left off. The saved progress is cleared when the flow finishes, when a purchase in it succeeds, and when the customer signs out or you identify a different user.

Analytics

Events a flow tracks are captured like your own events, with paywall_location and paywall_id added. Each screen the customer reaches in a flow is recorded as a screen view with paywall_location and flow_screen_index.

Offline

The SDK downloads each flow, verifies it, and keeps it on the device, so a flow shown once can be shown again offline. A location's assignment is cached for seven days, and locations the device has shown before are refreshed at launch. To make a flow available offline before it has ever been shown, list its location in preloadPlacements.

Next steps