Using the Superwall Delegate
Observe the paywall lifecycle and SDK events from shared Kotlin code.
Beta
The KMP SDK is in beta and its API may change between releases.
SuperwallDelegate is how you observe what the SDK is doing: paywalls opening and closing, subscription status changing, events being tracked, links being redeemed.
Every method has a default no-op implementation, so override only the ones you need.
Setting the delegate
import com.superwall.sdk.kmp.Superwall
import com.superwall.sdk.kmp.SuperwallDelegate
import com.superwall.sdk.kmp.models.paywall.PaywallInfo
class MyDelegate : SuperwallDelegate {
override fun didPresentPaywall(paywallInfo: PaywallInfo) {
println("Presented ${paywallInfo.name}")
}
override fun didDismissPaywall(paywallInfo: PaywallInfo) {
println("Dismissed ${paywallInfo.name}")
}
}
Superwall.delegate = MyDelegate()delegate is one of the few members you can set before configure. The value is stored
immediately and installed into the native SDK when configuration happens, so you will not miss
early events. Setting it to null clears it.
What you can observe
Paywall lifecycle
override fun willPresentPaywall(paywallInfo: PaywallInfo) {}
override fun didPresentPaywall(paywallInfo: PaywallInfo) {}
override fun willDismissPaywall(paywallInfo: PaywallInfo) {}
override fun didDismissPaywall(paywallInfo: PaywallInfo) {}Paywall interactions
override fun handleCustomPaywallAction(name: String) {}
override fun paywallWillOpenURL(url: String) {}
override fun paywallWillOpenDeepLink(url: String) {}State changes
override fun subscriptionStatusDidChange(from: SubscriptionStatus, to: SubscriptionStatus) {}
override fun customerInfoDidChange(from: CustomerInfo, to: CustomerInfo) {}
override fun userAttributesDidChange(newAttributes: Map<String, Any?>) {}Analytics and logging
override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) {}
override fun handleLog(
level: LogLevel,
scope: LogScope,
message: String?,
info: Map<String, Any?>?,
error: String?,
) {}Web checkout redemption
override fun willRedeemLink() {}
override fun didRedeemLink(result: RedemptionResult) {}Threading
Delegate callbacks are not guaranteed to arrive on any particular thread. Which thread a given callback lands on depends on the callback and the platform, and it may change between releases.
Treat every callback as if it could arrive on a background thread:
- Do not touch UI directly. Hop to the main thread yourself.
- Be thread-safe. Do not assume callbacks are serialized against each other.
- Be quick. A callback runs before the SDK continues, so blocking in one blocks the SDK.
If you need UI work from a callback, hop yourself. Give the delegate a scope to launch on:
class MyDelegate(private val scope: CoroutineScope) : SuperwallDelegate {
override fun subscriptionStatusDidChange(from: SubscriptionStatus, to: SubscriptionStatus) {
scope.launch(Dispatchers.Main) {
updateUi(to)
}
}
}Or skip the delegate for that case entirely and collect subscriptionStatusFlow from a main-dispatched scope, which keeps the threading question in one place.
Platform gap
handleSuperwallDeepLink is iOS only. superwall-android has no equivalent delegate hook, so it is never invoked on Android. See Platform differences.
// iOS only
override fun handleSuperwallDeepLink(
fullURL: String,
pathComponents: List<String>,
queryParameters: Map<String, String>,
) {}The delegate and the flows
Setting or clearing the delegate never uninstalls the SDK's own internal delegate, so subscriptionStatusFlow and customerInfoFlow keep working whether or not you have one set. Use whichever fits:
- Delegate when you want the full event firehose, or the paywall lifecycle.
- Flows when you want subscription state to drive UI, and you would rather not think about threads.
How is this guide?