Skip to content

Releases: Purchasely/Purchasely-Android

6.1.1

Choose a tag to compare

@kherembourg kherembourg released this 10 Sep 11:35

Purchasely Android SDK 6.1.1

✨ Improvements

  • Accessibility. TalkBack now announces the rendered text of a button or a label all the time.
  • Internal test and build maintenance, with no change to the public API.

Full changelog: 6.1.0...6.1.1

6.1.0

Choose a tag to compare

@kherembourg kherembourg released this 04 Sep 20:32

Purchasely Android SDK 6.1.0

6.1.0 is an additive release. There are no breaking changes, so an existing integration needs no code change.

The main feature is Web2App: your app now knows the result of a web subscription redemption, and can draw its own result screen.

🔗 Web-to-app funnels (redemption)

A user buys a subscription on your website, taps the link in the confirmation email, and lands in your app. The SDK now tells your app when the redemption settles.

Purchasely.Builder(applicationContext)
    .apiKey("your-api-key")
    .stores(listOf(GoogleStore()))
    .webRedemptionListener(appHandlesRedemptionAlert = false) { result ->
        when (result) {
            // `replay` is a re-tap of a link already redeemed. Unlock, but do not
            // thank the user twice.
            is PLYWebRedemptionResult.Success -> unlockContent(result.context, result.replay)
            is PLYWebRedemptionResult.Failure -> showError(result.errorMessage)
        }
    }
    .build()
    .start()

Your listener receives onRedemptionCompleted(result) on the main thread, exactly once per settled redemption. The result carries the outcome and what the redemption granted, so you can unlock the content immediately.

  • appHandlesRedemptionAlert = false (default): the SDK shows its own success or failure popin, then calls your listener when the user closes it.
  • appHandlesRedemptionAlert = true: the SDK shows no popin. Your listener receives the result as soon as the redemption settles, and your app owns the full post-redemption experience.

The listener is available on the builder only, because a redemption can settle during start(). The SDK holds it until Purchasely.close(), so do not let it capture an Activity.

PLYWebRedemptionResult is a sealed class with two cases:

  • Success(context, replay)context describes what the redemption granted, and its subscription carries the redeemed subscription when the server sent one. replay is true when the user taps a link that was already redeemed.
  • Failure(errorCode, errorMessage) — the server-provided code and message.

User attributes from the web funnel. A successful redemption can restore the built-in and custom user attributes of the web purchase. The SDK applies them before the entitlements refresh, so every later event and every audience already sees them. Read them with the getters you already use.

Three notes:

  • A redemption deeplink does not obey allowDeeplink. A user who taps the link in the email always gets the subscription.
  • When a link has expired, errorMessage can contain a masked email address, for example j***@example.com. Show it to the user. Do not send it to your analytics.
  • The redemption token never enters a log line, an analytics event, or the deeplink waiting list. Deduplication uses a SHA-256 hash of the token.

Two new analytics events: REDEMPTION_CONSUMED and REDEMPTION_FAILED. The SDK also sends the consumed event when the user taps an already redeemed link. A replay is a success, and the replay flag tells the two apart. Add the two cases if your app switches over the event type.

Read an event payload with event.properties.toMap(). The redemption payload types are opaque on purpose, so do not read their fields directly.

Documentation: Web-to-app funnels (redemption)

Also in this release

  • Set the anonymous user id yourself. anonymousUserId(id, override) on the builder gives the SDK the java.util.UUID your app already uses. The SDK keeps an id that is already on the device, unless you pass override = true. The SDK stores the id you pass in uppercase, and an id it generates itself in lowercase, so compare an anonymous user id case-insensitively.
  • API proxy for restricted regions. proxy(api) routes the API traffic through your own https URL, for a region where api.purchasely.io is not reachable. The paywall and the tracking hosts stay on production. proxy(api = null) clears a proxy that an earlier build() set.

Fixes

  • start() always invokes its callback, exactly once. A configure() that returned early no longer leaves the callback pending.
  • An inline PLYPresentationView no longer restores another paywall's state after a configuration change. A banner could render the full-screen paywall inside its banner-sized slot.
  • A screen that is re-attached no longer reports a second PRESENTATION_VIEWED.
  • Every handleDeeplink() of a session works. The consumed marker is scoped to the intent that carried the URL, so it no longer swallows every deeplink after the first one.
  • The redemption outcome alert reports to your listener when the user dismisses it.

Full list of the changes: docs.purchasely.com/changelog

6.0.2

Choose a tag to compare

@chouaibMo chouaibMo released this 28 Jul 10:14

Purchasely Android SDK 6.0.2

General performance improvements and bug fixes

6.0.1

Choose a tag to compare

@chouaibMo chouaibMo released this 20 Jul 14:43

Purchasely Android SDK 6.0.1

The stable release of the v6 major version. v6 modernizes the presentation API, reworks the action interceptor, makes storeless and observer-first the defaults, and tightens initialization. It supersedes the 6.0.0-rc.16.0.0-rc.3 release-candidate cycle — there is no separate 6.0.0 build; 6.0.1 is the version to integrate against.

This is a major release with breaking changes — read the full migration guide before upgrading from v5.

implementation("io.purchasely:core:6.0.1")
implementation("io.purchasely:google-play:6.0.1")
// implementation("io.purchasely:player:6.0.1") // optional

🆕 New in 6.0.1 (since 6.0.0-rc.3)

  • Theme mode is now configurable at initializationthemeMode(PLYThemeMode) is available on both Purchasely.Builder and the Kotlin DSL, applied before any paywall can render. Matches iOS. Purely additive: the default stays PLYThemeMode.SYSTEM, and runtime Purchasely.setThemeMode(...) after start() is unchanged.
    Purchasely {
        context(applicationContext)
        apiKey("your-api-key")
        themeMode(PLYThemeMode.DARK)
    }
    // or, fluent Builder:
    Purchasely.Builder(context).apiKey("").themeMode(PLYThemeMode.DARK).build().start()
  • setDefaultPresentationDismissHandler(...) now accepts null to unregister — the parameter is nullable, so a previously-set default dismiss handler can be cleared (passing null from Java no longer risks a runtime null-check). Matches iOS unregistration behavior.

All other work landed since rc.3 is internal (module restructuring, test hardening) and has no effect on the public API.


✨ Highlights & New Features

  • New PLYPresentation API — a complete builder / preload / display lifecycle with an observable state: StateFlow<PLYPresentationState> (Idle → Loading → Loaded → Displayed → Dismissed/Error). Preload early, display later, no extra network call.
  • PLYPresentationSession — every display(...) returns a session handle. Fire-and-forget from Java, or await() it from a coroutine to suspend until dismissal and get a PLYPresentationOutcome (structured concurrency + try/catch).
  • Granular action interceptorinterceptAction<PLYPresentationAction.Purchase> { … } replaces the monolithic setPaywallActionsInterceptor. Type-safe parameters per action, no casting. Available as a member of Purchasely in three forms:
    • reified Kotlin coroutine form: Purchasely.interceptAction<PLYPresentationAction.Purchase> { info, action -> … } (resolves from the Purchasely import — no extra import needed);
    • Kotlin callback form without coroutines: Purchasely.interceptAction(PLYPresentationAction.Purchase::class.java) { info, action, result -> … };
    • Java callback form (same Class-based overload).
  • Kotlin DSL entrypointPurchasely { context(...); apiKey(...); … } configures and starts in one block, with editor-time Lint checks (PurchaselyMissingContext, PurchaselyMissingApiKey, PurchaselyFullModeWithoutStores).
  • Storeless integration is now first-class — screens, analytics, campaigns, deeplinks, and user attributes all work with no store configured.
  • Automatic deeplink interception — the SDK reads the foreground activity's intent and routes its own URIs. No handleDeeplink() call required (opt out via automaticDeeplinkHandling).
  • Independent campaign gating — new allowCampaigns flag decouples campaign display from deeplink handling.
  • synchronize() completion callbackssynchronize(onSuccess, onError) for Observer mode; the subscriptions cache is refreshed before onSuccess fires.
  • Structured transition dimensionsPLYTransition supports px (dp) and percentage for drawer/popin height and popin width. A dimension explicitly set to 0px / 0% is honored as-is; an absent dimension falls back to the surface default (drawer 60%, popin 50%, width match_parent).
  • Theme modePLYThemeMode.LIGHT / DARK / SYSTEM via setThemeMode(...) / getThemeMode() at runtime, and (new in 6.0.1) via themeMode(...) at Builder/DSL init.
  • Logging — custom loggers now receive all messages regardless of logLevel; new logcatEnabled flag controls Logcat independently.

🔧 Behavioral Fixes

  • Observer-mode auto-sync on interceptor SUCCESS — in Observer mode, the SDK calls synchronize() automatically after a paywall Purchase or Restore action your interceptor handled and reported as SUCCESS, matching iOS. Do not call synchronize() yourself inside the interceptor — it would double-sync. display(...) then resolves to a PURCHASED outcome with the plan (or RESTORED) rather than CANCELLED with a null plan. (MOB-260)
  • open_presentation no longer drops your dismiss callback — when a paywall action navigates to another presentation that has no callback of its own, the original onDismissed / close callback is preserved instead of being overwritten by a no-op, so a later closeAllScreens() correctly reaches your handler. The secondary screen also reaches a terminal Dismissed state so observers no longer block indefinitely.
  • Default presentation dismiss handler fires for deeplinkssetDefaultPresentationDismissHandler(...) now delivers a full PLYPresentationOutcome (with presentation populated) and correctly fires for deeplink-triggered presentations, which the v5 handler never did.
  • Bottom safe area is opt-in and no longer doubled — the navigation-bar safe area is driven per component by safe_area_bottom (symmetric with safe_area_top), so it no longer stacks with an unconditional inset. Layouts that need bottom spacing must set safe_area_bottom on their bottom container.
  • User switch & logout/login — subscription refresh and dismissal on user switch are fixed; stale refresh jobs are cancelled so a fast identity change no longer races an in-flight refresh, and a fast logout()login() no longer lets the new user inherit the previous user's attributes.
  • Translations — corrected the Indonesian locale (idin, the JVM's legacy code) and added the missing web-checkout strings across languages.

⚠️ Breaking Changes (v5 → v6)

Initialization & running mode

  • Default running mode is now Observer (was Full). Add .runningMode(PLYRunningMode.Full) if you want Purchasely to handle/validate purchases. In Observer mode, presentations no longer auto-close after purchase/restore.
  • PLYRunningMode.PaywallObserverPLYRunningMode.Observer (rename).
  • apiKey is validated at start() — null/blank leaves the SDK inert and fires PLYError.Configuration.
  • Init callback signature simplifiedstart { error -> } (dropped the leading Boolean). Java: Function2<Boolean,PLYError,Unit>Function1<PLYError,Unit>.

Action interceptor

  • Removed setPaywallActionsInterceptor(), PLYPresentationInfo, PLYPaywallActionHandler/PLYCompletionHandler, PLYPaywallActionListener/PLYProcessActionListener.
  • PLYPresentationAction is now a sealed class (was enum); PLYPresentationActionParameters removed (params live on each subclass).
  • processAction(false/true)PLYInterceptResult.SUCCESS / NOT_HANDLED (+ new FAILED).
  • Kotlin reified interceptAction<T> requires consumer jvmTarget = 11 (or use the Class-based overload).

Presentation API

  • All presentation types moved to io.purchasely.ext.presentation.* (import-only change).
  • PLYPresentationProperties removed — configure via the builder/DSL.
  • Purchasely.presentationView(...) removedPLYPresentation { … }.preload { … }.
  • PLYPresentation.idscreenId (also toMap() key "id""screenId").
  • Suspend display() extension removedLoaded.display() is now non-suspend.
  • onCloseonCloseRequested; PLYPresentationClosePLYPresentationCloseRequested.
  • Display callbacks now receive a single PLYPresentationOutcome (carries purchaseResult, plan, closeReason, error) instead of (result, plan) + separate error.
  • back()/close() are now on PLYPresentation (Loaded) only.
  • setDefaultPresentationResultHandlersetDefaultPresentationDismissHandler (rename; delivers a full PLYPresentationOutcome). The analytics presence marker is renamed accordingly (DEFAULT_PRESENTATION_RESULT_HANDLERDEFAULT_PRESENTATION_DISMISSED_HANDLER).
  • PLYProductViewResult deprecatedPLYPurchaseResult.

Removed surfaces

  • Subscription list & cancellation survey UI fully removed — subscriptionsFragment(), all PLYSubscription*/cancellation fragments & views, the ply/subscriptions & ply/cancellation_survey deeplinks, and 5 related PLYEvent subclasses. Build custom UI from userSubscriptions() / userSubscriptionsHistory().
  • Purchasely.displaySubscriptionCancellationInstruction(...) removed — this helper dialog is dropped as part of aligning the Android surface with iOS. Route users to the store's native manage-subscription screen (e.g. Google Play's Manage subscriptions).
  • Purchase historypurchaseHistory() and isPastSubscriber() removed → userSubscriptionsHistory().
  • PLYPlan intro* methods removedoffer* equivalents (direct rename).
  • PLYPlanTags INTRO_* / TRIAL_* removedOFFER_*.

Behavioral changes

  • allowDeeplink now defaults to true (was false). Set .allowDeeplink(false) to keep v5 deferred behavior. Preview deeplinks (?preview=1) always display immediately.
  • Storeless errors — purchase/restore now return PLYError.NoStoreConfigured (was PLYError.Unknown "No store found").
  • User attribute mutators return Deferred<Boolean> (`setUserAttribut...
Read more

6.0.0-rc.3

Choose a tag to compare

@chouaibMo chouaibMo released this 08 Jul 17:01

Warning

v6 is currently in a release-candidate phase — use 6.0.0-rc.3, not 6.0.0. 6.0.0-rc.3 is the official build to integrate against for early integration and testing while v6 stabilizes. Use it to validate your migration and report any issues; the stable 6.0.0 will be announced once the RC cycle completes.

implementation("io.purchasely:core:6.0.0-rc.3")
implementation("io.purchasely:google-play:6.0.0-rc.3")
// implementation("io.purchasely:player:6.0.0-rc.3") // optional

Purchasely Android SDK 6.0.0-rc.3

The third release candidate of the v6 major version. It builds on 6.0.0-rc.2 with bug fixes, API refinements, and a toolchain bump. The full set of v6 breaking changes is documented in the migration guide; the sections below cover only what changed since rc.2.

⚠️ Breaking Changes (since rc.2)

  • interceptAction<T> / removeActionInterceptor<T> are now members of Purchasely, not top-level extension functions. If an earlier 6.0.0-rc build had you add an explicit import for the reified Kotlin overloads, delete it — the member call now resolves from the Purchasely import you already have. No call-site change is needed.
    - import io.purchasely.ext.interceptAction        // remove
    - import io.purchasely.ext.removeActionInterceptor // remove
    
    Purchasely.interceptAction<PLYPresentationAction.Purchase> { info, purchase -> … } // unchanged
  • Purchasely.displaySubscriptionCancellationInstruction(activity, themeId) removed. This helper dialog is dropped as part of aligning the Android public surface with iOS. Route users to the store's native manage-subscription screen instead (e.g. Google Play's Manage subscriptions).

✨ Highlights & Fixes

  • Observer-mode auto-sync on interceptor SUCCESS — in Observer mode, the SDK now calls synchronize() automatically after a paywall Purchase or Restore action that your interceptor handled and reported as SUCCESS, matching iOS. Previously the transaction was never reported unless you called synchronize() yourself, corrupting analytics and subscription state. Remove any manual synchronize() you added inside the interceptor — it would now double-sync. (MOB-260)
  • open_presentation no longer drops your dismiss callback — when a paywall action navigates to another presentation that has no callback of its own, the original onDismissed / close callback is now preserved instead of being overwritten by a no-op, so a later closeAllScreens() correctly reaches your handler. The secondary screen also reaches a terminal Dismissed state so observers no longer block indefinitely.
  • Bottom safe area is opt-in again and no longer doubled — the navigation-bar safe area is now driven per component by safe_area_bottom (symmetric with safe_area_top). A previously unconditional inset stacked with the per-component one, doubling the CTA-to-bottom gap — large on 3-button-navigation devices, small on gesture-navigation ones. Layouts that need bottom spacing must set safe_area_bottom on their bottom container, as they already do for safe_area_top.
  • Kotlin interceptor without coroutines — a new Class-based overload lets Kotlin callers register an interceptor without a suspend lambda, returning the outcome via a result(…) callback: Purchasely.interceptAction(PLYPresentationAction.Purchase::class.java) { info, action, result -> … }. The reified coroutine form and the Java overload are unchanged.
  • Translations — corrected the Indonesian locale (idin, the JVM's legacy code) and added the missing web-checkout strings across languages.

📦 Publishing & Artifacts

  • Fat-AAR now declares its external dependencies — the published :core POM and Gradle module metadata now correctly list kotlinx-serialization-json and androidx.core:core-ktx, which were previously stripped along with the internal modules merged into the AAR. Consumers no longer risk missing these transitive dependencies.
  • Javadoc jar populated again — the published -javadoc.jar was effectively empty in rc.2; it now ships the full Dokka output, so IDE documentation resolves against the artifact.

🛠 Build Requirements

  • compileSdk 36 (Android 16) — up from 35. targetSdk stays 35, so there is no runtime behavior change.
  • Kotlin 2.3.21 toolchain — up from 2.2.x, picked up via AGP 9's built-in Kotlin. The published modules still pin languageVersion to 2.0, so consuming apps only need Kotlin 2.0+ — no consumer change required.
  • Bundled bumps: kotlinx-coroutines 1.11.0, kotlinx-serialization 1.11.0, Dokka 2.2.0, Gradle wrapper 9.6.1.
  • Unchanged from rc.2: Java target 11, minimum Gradle 9.3.0.

6.0.0-rc.2

Choose a tag to compare

@chouaibMo chouaibMo released this 23 Jun 21:24

Warning

v6 is currently in a release-candidate phase — use 6.0.0-rc.2, not 6.0.0. 6.0.0-rc.2 is the official build to integrate against for early integration and testing while v6 stabilizes. Use it to validate your migration and report any issues; the stable 6.0.0 will be announced once the RC cycle completes.

implementation("io.purchasely:core:6.0.0-rc.2")
implementation("io.purchasely:google-play:6.0.0-rc.2")
// implementation("io.purchasely:player:6.0.0-rc.2") // optional

Purchasely Android SDK 6.0.0-rc.2

The second release candidate of the v6 major version. It builds on 6.0.0-rc.1 with bug fixes and one API rename. The full set of v6 breaking changes is documented in the migration guide; the sections below cover only what changed since rc.1.

⚠️ Breaking Changes (since rc.1)

  • setDefaultPresentationResultHandlersetDefaultPresentationDismissHandler (rename, same signature). Matches iOS and reflects that it delivers a full PLYPresentationOutcome (with presentation populated), not a bare result. The handler now also correctly fires for deeplink presentations (previously it never did). The analytics presence marker is renamed accordingly (DEFAULT_PRESENTATION_RESULT_HANDLERDEFAULT_PRESENTATION_DISMISSED_HANDLER).

✨ Highlights & Fixes

  • Observer-mode purchase outcomes — when a paywall purchase/restore is handled by your action interceptor (returns SUCCESS) and reported via synchronize(), display(...) now resolves to a PURCHASED outcome with the plan (or RESTORED), instead of CANCELLED with a null plan. Mirrors Full-mode behavior.
  • User switch refresh & dismissal — fixed the subscription refresh and dismissal flow on user switch; stale subscription-refresh jobs are now cancelled so a fast identity change no longer races an in-flight refresh.
  • Logout → login attribute leak — logout cleanup (built-in attributes and, when requested, user attributes) now runs outside the cancellable refresh job, so a fast logout()login() sequence no longer lets the new user inherit the previous user's attributes. Built-in cleanup runs off the caller thread to avoid StrictMode/jank.
  • Transition dimensions honor 0 — a drawer/popin height or popin width configured to 0px / 0% is now rendered as-is instead of silently falling back to the surface default (drawer 60%, popin 50%, width match_parent). An absent dimension still uses the default.

🛠 Build Requirements

  • Unchanged from rc.1: Minimum Gradle 9.3.0, Kotlin 2.2.x (K2), Java target 11.

5.7.5

Choose a tag to compare

@chouaibMo chouaibMo released this 18 Jun 10:17

Bug fixes

  • fixed videos rendering outside the paywall in popin presentations — most visible on tablets, where a video could also appear duplicated in the background behind the popin. Videos now render correctly within the paywall bounds.

6.0.0-rc.1

Choose a tag to compare

@chouaibMo chouaibMo released this 12 Jun 17:52

Warning

v6 is currently in a release-candidate phase — use 6.0.0-rc.1, not 6.0.0. 6.0.0-rc.1 is the official build to integrate against for early integration and testing while v6 stabilizes. Use it to validate your migration and report any issues; the stable 6.0.0 will be announced once the RC cycle completes.

implementation("io.purchasely:core:6.0.0-rc.1")
implementation("io.purchasely:google-play:6.0.0-rc.1")
// implementation("io.purchasely:player:6.0.0-rc.1") // optional

Purchasely Android SDK 6.0.0-rc.1

The first release candidate of the v6 major version. v6 modernizes the presentation API, reworks the action interceptor, makes storeless and observer-first the defaults, and tightens initialization. This is a major release with breaking changes — read the full migration guide before upgrading.

✨ Highlights & New Features

  • New PLYPresentation API — a complete builder / preload / display lifecycle with an observable state: StateFlow<PLYPresentationState> (Idle → Loading → Loaded → Displayed → Dismissed/Error). Preload early, display later, no extra network call.
  • PLYPresentationSession — every display(...) returns a session handle. Fire-and-forget from Java, or await() it from a coroutine to suspend until dismissal and get a PLYPresentationOutcome (structured concurrency + try/catch).
  • Granular action interceptorinterceptAction<PLYPresentationAction.Purchase> { … } replaces the monolithic setPaywallActionsInterceptor. Type-safe parameters per action, no casting.
  • Kotlin DSL entrypointPurchasely { context(...); apiKey(...); … } configures and starts in one block, with editor-time Lint checks (PurchaselyMissingContext, PurchaselyMissingApiKey, PurchaselyFullModeWithoutStores).
  • Storeless integration is now first-class — screens, analytics, campaigns, deeplinks, and user attributes all work with no store configured.
  • Automatic deeplink interception — the SDK reads the foreground activity's intent and routes its own URIs. No handleDeeplink() call required (opt out via automaticDeeplinkHandling).
  • Independent campaign gating — new allowCampaigns flag decouples campaign display from deeplink handling.
  • synchronize() completion callbackssynchronize(onSuccess, onError) for Observer mode; the subscriptions cache is refreshed before onSuccess fires.
  • Structured transition dimensionsPLYTransition supports px (dp) and percentage for drawer/popin height and popin width.
  • Logging — custom loggers now receive all messages regardless of logLevel; new logcatEnabled flag controls Logcat independently.

⚠️ Breaking Changes

Initialization & running mode

  • Default running mode is now Observer (was Full). Add .runningMode(PLYRunningMode.Full) if you want Purchasely to handle/validate purchases. In Observer mode, presentations no longer auto-close after purchase/restore.
  • PLYRunningMode.PaywallObserverPLYRunningMode.Observer (rename).
  • apiKey is validated at start() — null/blank leaves the SDK inert and fires PLYError.Configuration.
  • Init callback signature simplifiedstart { error -> } (dropped the leading Boolean). Java: Function2<Boolean,PLYError,Unit>Function1<PLYError,Unit>.

Action interceptor

  • Removed setPaywallActionsInterceptor(), PLYPresentationInfo, PLYPaywallActionHandler/PLYCompletionHandler, PLYPaywallActionListener/PLYProcessActionListener.
  • PLYPresentationAction is now a sealed class (was enum); PLYPresentationActionParameters removed (params live on each subclass).
  • processAction(false/true)PLYInterceptResult.SUCCESS / NOT_HANDLED (+ new FAILED).
  • Kotlin reified interceptAction<T> requires consumer jvmTarget = 11 (or use the Class-based overload).

Presentation API

  • All presentation types moved to io.purchasely.ext.presentation.* (import-only change).
  • PLYPresentationProperties removed — configure via the builder/DSL.
  • Purchasely.presentationView(...) removedPLYPresentation { … }.preload { … }.
  • PLYPresentation.idscreenId (also toMap() key "id""screenId").
  • Suspend display() extension removedLoaded.display() is now non-suspend.
  • onCloseonCloseRequested; PLYPresentationClosePLYPresentationCloseRequested.
  • Display callbacks now receive a single PLYPresentationOutcome (carries purchaseResult, plan, closeReason, error) instead of (result, plan) + separate error.
  • back()/close() are now on PLYPresentation (Loaded) only.
  • PLYProductViewResult deprecatedPLYPurchaseResult.

Removed surfaces

  • Subscription list & cancellation survey UI fully removed — subscriptionsFragment(), all PLYSubscription*/cancellation fragments & views, the ply/subscriptions & ply/cancellation_survey deeplinks, and 5 related PLYEvent subclasses. Build custom UI from userSubscriptions() / userSubscriptionsHistory().
  • Purchase historypurchaseHistory() and isPastSubscriber() removed → userSubscriptionsHistory().
  • PLYPlan intro* methods removedoffer* equivalents (direct rename).
  • PLYPlanTags INTRO_* / TRIAL_* removedOFFER_*.

Behavioral changes

  • allowDeeplink now defaults to true (was false). Set .allowDeeplink(false) to keep v5 deferred behavior. Preview deeplinks (?preview=1) always display immediately.
  • Storeless errors — purchase/restore now return PLYError.NoStoreConfigured (was PLYError.Unknown "No store found").
  • User attribute mutators return Deferred<Boolean> (setUserAttribute, clearUserAttribute, increment/decrementUserAttribute, etc.) — safe to ignore.

🛠 Build Requirements

  • Minimum Gradle 9.3.0, Kotlin 2.2.x (K2), Java target 11.

5.7.4

Choose a tag to compare

@kherembourg kherembourg released this 04 May 10:45

Bug fixes

  • Anonymous → identified user transfer in Observer mode — purchases made before login are now correctly carried over to the user account on userLogin() when the SDK runs in PaywallObserver mode. Previously, receipts reported through synchronize() did not flag the user as having purchased, so the transfer step was skipped at next login.
  • Screen re-render crash — fixed IllegalStateException: The specified child already has a parent that could be thrown when a screen was rebuilt in rapid succession (orientation changes, repeated rendering). Children are now safely detached and released before being re-added.
  • Modal/Drawer flow crash — fixed an IllegalStateException raised when the modal or drawer from a Flowstate changed after the host fragment had been detached (e.g. fast back-press or activity teardown). The callback now no-ops instead of dereferencing a detached activity.

5.7.3

Choose a tag to compare

@kherembourg kherembourg released this 16 Mar 17:46

🐛 Bug Fixes

Flow system — process death survival

The flow system now correctly saves and restores its full navigation state when Android kills and restores the process. No more blank screens or lost navigation history after the OS reclaims memory in the background.

Flow system — purchase callback reliability

Fixed an issue where the display() callback could return CANCELLED instead of PURCHASED after a successful in-app purchase inside a multi-step flow.

Paywalls — plan picker race condition

Fixed an intermittent bug where plan picker labels could display the wrong plan's price (e.g. annual price on the monthly label) when Google Play products hadn't loaded yet.

WebView — crash on non-exported activities

Tapping a link inside a paywall WebView no longer crashes when a third-party app (e.g. OPay) registers a non-exported Activity for that URL scheme. The SDK now gracefully falls back to loading the URL in the WebView.