Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encorekit.com/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Encore.placement(id).show() is a suspend function that returns a PresentationResult. You can either branch on it at the call site (async-result) or register global onPurchaseRequest / onPassthrough handlers (handler-based). Both work; pick whichever fits the call site. For the cross-platform decision tree, see Integration Patterns.

Async-result pattern

Use this when the place you call show() from already has access to your purchase + decline logic.

Basic example

import androidx.lifecycle.lifecycleScope
import com.encorekit.encore.Encore
import com.encorekit.encore.features.offers.PresentationResult
import kotlinx.coroutines.launch

class CancelActivity : AppCompatActivity() {
    fun onCancelTapped() {
        lifecycleScope.launch {
            val result = Encore.placement("cancel_flow").show()
            when (result) {
                is PresentationResult.Completed -> {
                    // User completed an offer — navigate home
                    finish()
                }
                is PresentationResult.Dismissed -> {
                    // User dismissed — proceed with original flow
                    proceedWithCancellation(result.reason)
                }
                is PresentationResult.NoOffers -> {
                    // No offers available — proceed with original flow
                    proceedWithCancellation(null)
                }
            }
            // Code that runs in all branches lives here, written once
            analytics.track("cancel_flow_resolved")
        }
    }
}

From Compose

onClick is synchronous, so launch a coroutine on the lifecycle scope:
@Composable
fun CancelButton() {
    val activity = LocalContext.current as ComponentActivity
    val scope = rememberCoroutineScope()

    Button(onClick = {
        scope.launch {
            val result = Encore.placement("cancel_flow").show()
            when (result) {
                is PresentationResult.Completed -> activity.finish()
                is PresentationResult.Dismissed,
                is PresentationResult.NoOffers -> proceedWithCancellation()
            }
        }
    }) { Text("Cancel Subscription") }
}

Result type

sealed class PresentationResult {
    data class Completed(val offerId: String, val campaignId: String?) : PresentationResult()
    data class Dismissed(val reason: DismissReason) : PresentationResult()
    data object NoOffers : PresentationResult()
}

enum class DismissReason(val value: String) {
    USER_CLOSED("user_closed"),
    NO_OFFERS("no_offer_available"),
    ERROR("error"),
}
See PresentationResult.

Handler pattern

Use this when the show() call site is a third-party delegate or a UI action that doesn’t have direct access to your purchase code.

Register in Application.onCreate()

import android.app.Application
import com.encorekit.encore.Encore

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        Encore.configure(this, apiKey = "pk_live_...")

        Encore.shared.onPurchaseRequest { request ->
            // Route to RevenueCat / Adapty / Play Billing
            billingManager.purchase(request.productId)
        }

        Encore.shared.onPassthrough { placementId ->
            // Resume the user's original flow
            AppRouter.handlePassthrough(placementId)
        }
    }
}

Then show() from anywhere

Button(onClick = {
    scope.launch { Encore.placement("cancel_flow").show() }
}) { Text("Cancel Subscription") }
The handlers fire when the placement resolves. Code at the call site doesn’t need to inspect the returned result.

When to use which

  • Async-result — call site already imports your billing client. Branch-specific code lives at the call site.
  • Handler — Encore is invoked from third-party paywall delegates or from many UI sites that share post-purchase logic.
  • Mixed — handlers handle cross-cutting analytics; specific call sites still branch on show().
See the decision tree for the full rationale.

Re-registration semantics

Encore.shared.onPurchaseRequest { /* v1 */ }
Encore.shared.onPurchaseRequest { /* v2 — replaces v1 */ }
Each setter replaces the previous lambda. No double-fires.

Platform-specific notes

  • show() is suspend — call from a coroutine scope (lifecycleScope, viewModelScope, rememberCoroutineScope()). The no-arg overload auto-resolves the current foreground Activity; the explicit show(activity: Activity) overload is also available if you need to bind to a specific Activity.
  • Fire-and-forget overloadEncore.placement(id).show(scope) exists if you don’t want to await the result. Equivalent to launching with the handler pattern.
  • PresentationResult.Completed carries offerId and campaignId — the entitlement claim is applied by the SDK; the result fields are for your own analytics.
  • Dismissed vs NoOffersNoOffers fires when the API returns zero offers for this user; Dismissed(DismissReason.USER_CLOSED) fires when the user closes a sheet that did have offers. Dismissed(DismissReason.ERROR) covers presentation failures (e.g., no foreground Activity).
  • JVM target — the SDK is built with JVM target 17. Your app must match (most modern Android Gradle plugins default to this).

See also