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 forced onto the main thread. They arrive on whatever thread the native SDK called from, and that is not the same on both platforms.

  • Paywall lifecycle hooks arrive on the main thread on Android and iOS. Update UI from these directly.
  • State-change hooks (subscriptionStatusDidChange, customerInfoDidChange, userAttributesDidChange, willRedeemLink, didRedeemLink) arrive on a background thread on Android, and on the main thread on iOS.
  • handleSuperwallEvent and handleLog are not guaranteed either way on Android. Neither adds a dispatcher hop, so they run on whatever thread the SDK called from. handleLog is invoked inline wherever a log statement executes, which includes the main thread. On iOS both are on main.

Two things this asks of your implementation:

  1. Be thread-safe. The analytics hooks are not serialized against each other.
  2. Be quick. They run synchronously on an SDK thread, so blocking in one slows the SDK.

If you need UI work from an analytics hook, hop yourself:

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?

On this page