Present your first paywall
Register a placement and present a Superwall paywall from shared code.
Beta
The KMP SDK is in beta and its API may change between releases.
Register a placement
Paywalls are presented by registering a placement. You do not tell the SDK to show a paywall. You tell it a placement occurred, and your campaign on the dashboard decides what happens.
Superwall.register(placement = "campaign_trigger")That one line is a complete integration. Whether a paywall appears, which one, and to whom, is all configured on the dashboard without shipping an app update.
The placement name must match one you have added to a campaign on the
dashboard. A name that is not in any campaign resolves to
PaywallSkippedReason.PlacementNotFound.
Gate a feature behind it
Pass a feature closure to run code that should only happen if the user is entitled to it:
Superwall.register(placement = "campaign_trigger") {
launchTheFeature()
}When the closure runs depends on the paywall's feature-gating behavior: immediately when no paywall shows, or after a purchase or restore when the placement is gated. Feature gating covers the rules.
Pass parameters
params are usable in audience filters and on the paywall itself:
Superwall.register(
placement = "campaign_trigger",
params = mapOf(
"source" to "onboarding",
"isTrialEligible" to true,
),
)Values may be String, Boolean, Long, Double, List, Map, or Set. Anything else is stringified.
Observe what happened
A PaywallPresentationHandler reports on the presentation. Set only the closures you care about; unset ones are never invoked.
import com.superwall.sdk.kmp.PaywallPresentationHandler
import com.superwall.sdk.kmp.models.results.PaywallResult
import com.superwall.sdk.kmp.models.results.PaywallSkippedReason
val handler = PaywallPresentationHandler()
handler.onPresent { info ->
println("Presented ${info.name}")
}
handler.onDismiss { info, result ->
when (result) {
is PaywallResult.Purchased -> println("Purchased ${result.productId}")
is PaywallResult.Restored -> println("Restored")
is PaywallResult.Declined -> println("Declined")
}
}
handler.onSkip { reason ->
when (reason) {
is PaywallSkippedReason.Holdout -> println("Holdout: ${reason.experiment.id}")
is PaywallSkippedReason.NoAudienceMatch -> println("No audience match")
is PaywallSkippedReason.PlacementNotFound -> println("Placement not found")
}
}
handler.onError { error ->
println("Paywall error: $error")
}
Superwall.register(
placement = "campaign_trigger",
handler = handler,
) {
launchTheFeature()
}onSkip is not a failure path. A holdout or an unmatched audience filter means your campaign
worked as configured. The user was not meant to see a paywall.
All handler closures and the feature closure are delivered on the main thread, on both platforms. You can touch UI from them directly.
Custom callbacks
onCustomCallback is the one closure that returns a value. A paywall can request an action from your app and branch on the result:
import com.superwall.sdk.kmp.models.callbacks.CustomCallbackResult
handler.onCustomCallback { callback ->
when (callback.name) {
"validate_email" -> {
val email = callback.variables?.get("email") as? String
if (isValidEmail(email)) {
CustomCallbackResult.success(mapOf("validated" to true))
} else {
CustomCallbackResult.failure(mapOf("error" to "Invalid email"))
}
}
else -> CustomCallbackResult.failure()
}
}It is a suspend closure, so you can do real work in it. When it is unset, the SDK responds with CustomCallbackResult.failure().
Check before you register
getPresentationResult previews what registering would do without presenting anything, which is useful for adjusting UI ahead of time, like hiding an upgrade button for users who would not see a paywall:
val result = Superwall.getPresentationResult(placement = "campaign_trigger")Controlling the paywall
A few members act on the presented paywall:
Superwall.dismiss() // suspend; resumes once dismissed
Superwall.isPaywallPresented // Boolean
Superwall.latestPaywallInfo // PaywallInfo?
Superwall.togglePaywallSpinner(isHidden = true)Preloading
Paywalls preload by default. To take over the timing, disable it and preload yourself:
Superwall.configure(
apiKey = "pk_your_api_key",
options = SuperwallOptions(paywalls = PaywallOptions(shouldPreload = false)),
)
// Later
Superwall.preloadAllPaywalls()
Superwall.preloadPaywalls(placementNames = setOf("campaign_trigger"))Next, manage your users.
How is this guide?