Overview
SDK 2.0 is a breaking release that makes the integration surface simpler and harder to misuse:show()returns one result record and never throws. Errors arrive as values on the result instead of as exceptions.- Purchases run through a registered
EncorePurchaseController. The SDK never runs purchase code you didn’t write; the built-in Play Billing fallback is removed. - Global handler closures are retired. Results are values at the call site; passive observation moves to the
Encore.outcomesstream. - The callback form is always delivered on the main thread; the suspending form resumes in your caller’s context.
show()itself is safe to call from any thread or scope.
Step 1: Update the dependency
Step 2: Register a purchase controller
This is the largest change. In 1.x, purchases ran through theonPurchaseRequest closure, and if no handler was registered the SDK bought the product itself through Play Billing and reported it via onPurchaseComplete. 2.0 removes the closure, the completion callback, and the built-in Play Billing fallback: that fallback bypassed whatever receipt validation, restore handling, and entitlement bookkeeping your app already has, and it made a missing registration look like a working purchase instead of a misconfiguration.
Implement the controller and pass it to configure():
Purchased, Cancelled, or Pending; throw for real failures. The full contract is in EncorePurchaseController.
Step 3: Update show() call sites
show() returns the full result record and never throws. The 1.x scope-taking overloads become an explicit callback form:
Step 4: Update result handling
1.x modeled the result as a flat triad (Completed, Dismissed(reason), NoOffers), which forced a claim and a purchase to compete for a single winning case. 2.0 records the facts independently:
2.0 splits the result into NotPresented(reason) and Presented(outcome); see PresentationResult for the full shape.
The record carries raw facts only: there is no SDK-computed “unlocked” verdict, so your integration decides what a claim means. Branch on the facts your flow cares about:
result.claim surfaces the claimed offer whenever the advertiser funnel reached Claimed or Verified; read result.advertiser directly to tell the two apart.
When the mechanism detail matters, read the record:
Step 5: Replace global callbacks with the outcomes stream
onPassthrough and onPurchaseComplete are removed. Handle fallback inline at the call site (Step 4), and move passive observation (analytics, logging, cross-cutting state) to the Encore.outcomes stream:
show() resolution lands on Encore.outcomes, including NotPresented, plus strict-unlock verifications that settle on a later launch.
Removed APIs at a glance
Behavior changes to be aware of
These compile fine but behave differently at runtime:- Claim-then-purchase records both funnels. 1.x collapsed the flow to a single winner. 2.0 carries
advertiser = Claimed(...)andpublisher = Purchasedside by side, and the result is delivered once, after the purchase resolves (later than 1.x fired its callbacks). - Strict mode records the claim, and verification upgrades it. With
UnlockMode.Strict, the record saysadvertiser = Claimed(...)immediately, and in-session verification upgrades it toVerified(...). A claim that ends unverified re-verifies across launches and surfaces asPlacementOutcome.StrictUnlockVerifiedonEncore.outcomes; the persistence rules are in unlock modes. - The offer sheet no longer recreates on rotation or dark-mode changes, so mid-flow configuration changes can’t produce spurious results, and a dead flow (killed sheet) resolves as
Presented(dismissal = Interrupted)instead of wedgingshow()in “already presenting”. - Gesture dismissal now reports
swipe_dismissin analytics (previously lumped intoclose_button). On the SDUI sheet, which is the default render path, this covers both dragging the sheet down and tapping outside it; on the native fallback sheet only dragging reports it. All other event names and close-reason strings are unchanged on the wire.
Encore.placement("streak_complete").useCase(UseCase.REWARD_USERS)) with per-presentation headline() / subheadline() copy overrides. The surface fires after the user has already done something worth celebrating, and reward presentations never run a purchase and never show the paywall layout.
Verify the migration
Confirm each of these before shipping:
- The project compiles with no references to removed 1.x APIs.
- If a subscription product is configured for your app, an
EncorePurchaseControlleris registered atconfiguretime. - Every fallback path (your original paywall or flow) runs off the raw facts (
result.claim == null && result.publisher != PublisherOutcome.Purchased), not a removed callback. - If you subscribe to
Encore.outcomes, the subscription starts at app startup (there is no replay). - Test the full flow on a device: present a placement, claim an offer, complete a purchase, and dismiss without acting. Each path should resolve exactly once with the result you expect.
If you hit anything this guide doesn’t cover, reach out to admin@encorekit.com.