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:

  1. Do not touch UI directly. Hop to the main thread yourself.
  2. Be thread-safe. Do not assume callbacks are serialized against each other.
  3. 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?

On this page