Skip to main content

Overview

Encore lets you present a premium brand offer (a real perk a brand funds to acquire customers, such as a free trial, discount, or credit) at critical moments like cancellation flows or feature paywalls. The native SDK renders all offer UI natively (SwiftUI on iOS, Jetpack Compose on Android); the Flutter layer triggers presentation and receives the result. show() completes with the complete factual record of what happened. There are no global handlers to register: control flow lives at the call site.

Present an offer

show() never throws. Every failure, including “nothing was presented”, is a value on the returned EncorePresentationResult.

Select a use case

useCase() selects which Encore surface the placement presents. It defaults to EncoreUseCase.reduceChurn, so an existing integration that never calls it is unaffected.
The identifiers name the capability; nativeValue carries the sdui_use_case label the backend stores. The two are pinned explicitly rather than derived from each other, so renaming a case can never silently change what goes over the wire. Pass the case, not a string built from its name.
A use case with no enabled variant resolves EncoreNotPresented(useCaseUnavailable) rather than falling back to the churn-intervention sheet: an in-app-purchase screen at a moment the user was meant to be rewarded is worse than presenting nothing. See Reward Users.

Copy overrides

headline() and subheadline() override the sheet’s copy for one presentation, on every use case. The native side writes the value into the variable the active template reads. Priority order, first one set wins: this value, then the copy Encore resolved for your app, then the shipped template default. A blank string is ignored, so the chain falls through rather than rendering an empty line. You pass the finished string, so localization, pluralization, wording, and emoji stay yours. See Where the copy comes from.

Read the result

EncorePresentationResult is a sealed pair, so Dart 3 gives you an exhaustive switch:
Fact readers give you one axis at a time when you don’t want to pattern-match the whole record:
A presented record carries two independent funnels plus how the sheet went away: One presentation can claim an offer and run a purchase, so both are recorded rather than competing for one winning case. notAttempted is a real value, meaning “the funnel was open and nothing entered it”, never null. claim carries the offer itself:
transactionId is the one to hold on to. The brand-side completion lands later, sometimes days later, and reaches your server through the offer-completed webhook, which carries the same value and treats it as the idempotency key. It is the only way to match the result your app just received against the completion it eventually produces.
There is deliberately no SDK-computed “unlocked” verdict. What a claim means is a property of the variant flow that served it, so any projection over app-global config can contradict the flow that actually ran. Branch on the raw axes.

When nothing was presented

EncoreNotPresented is not a synonym for failure: Run your fallback for all of them, and log only the error case.

Purchases

A user who accepts an offer that carries an in-app purchase triggers your registered EncorePurchaseController. It is registered at configure; see Configure the SDK.
Return pending for deferred purchases. Ask to Buy on iOS, or SCA or a pending Play transaction on Android, means the user has neither bought nor abandoned; the store’s webhook settles it later. Returning cancelled for these silently misreports revenue, recording a live sale as a decline.

Observe outcomes globally

For analytics forwarding, app-owned state, or a debug overlay, listen to the outcomes stream. It is multicast, so several listeners can subscribe at once.
There is no replay, so subscribe at startup.

Full Example


Next Steps