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()
}| Result | When |
|---|---|
Purchased | The customer bought a product in the flow. productId is the store product ID |
Restored | The customer restored their purchases from the flow |
Finished | The flow reached one of its finish actions. result names it; variables holds the answers |
Closed | The flow's close action, the back button, dismissFlow(), or another flow replacing it |
Failed | The 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:
| Reason | Meaning |
|---|---|
NOT_ASSIGNED | No flow is assigned to the location for this customer |
UNAVAILABLE | Nothing is cached and Voidhash is unreachable, or the flow could not download |
RENDERER_MISSING | paywallRenderer is not set |
UNSUPPORTED_PACKAGE | The flow was published for a newer SDK; update the SDK |
PRESENTATION_FAILED | The flow could not be shown, or stopped while on screen |
DISABLED | The 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.
| Capability | Lets a flow |
|---|---|
LINKS | Open links in the browser or the app that handles them. Granted by default |
ATTRIBUTES | Set attributes on the current person, like setPersonAttributes |
NOTIFICATIONS | Ask the customer for permission to send notifications |
TRACKING | Ask 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.