Skip to content

6.0.1

Choose a tag to compare

@chouaibMo chouaibMo released this 20 Jul 14:43
· 3 commits to master since this release

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> (setUserAttribute, clearUserAttribute, increment/decrementUserAttribute, etc.) — safe to ignore.

🛠 Build Requirements

  • compileSdk 36 (Android 16), targetSdk 35 (no runtime behavior change), minSdk 23.
  • Kotlin 2.3.21 toolchain — picked up via AGP 9's built-in Kotlin. The published modules pin languageVersion to 2.0, so consuming apps only need Kotlin 2.0+ — no consumer change required.
  • Java target 11. Minimum Gradle 9.3.0 (wrapper 9.6.1).
  • Bundled: kotlinx-coroutines 1.11.0, kotlinx-serialization 1.11.0, Dokka 2.2.0.

📖 Full migration guide

https://docs.purchasely.com/docs/migrating-from-v5-to-v6-android