6.0.1
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.1 → 6.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 initialization —
themeMode(PLYThemeMode)is available on bothPurchasely.Builderand the Kotlin DSL, applied before any paywall can render. Matches iOS. Purely additive: the default staysPLYThemeMode.SYSTEM, and runtimePurchasely.setThemeMode(...)afterstart()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 acceptsnullto unregister — the parameter is nullable, so a previously-set default dismiss handler can be cleared (passingnullfrom 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
PLYPresentationAPI — a complete builder / preload / display lifecycle with an observablestate: StateFlow<PLYPresentationState>(Idle → Loading → Loaded → Displayed → Dismissed/Error). Preload early, display later, no extra network call. PLYPresentationSession— everydisplay(...)returns a session handle. Fire-and-forget from Java, orawait()it from a coroutine to suspend until dismissal and get aPLYPresentationOutcome(structured concurrency +try/catch).- Granular action interceptor —
interceptAction<PLYPresentationAction.Purchase> { … }replaces the monolithicsetPaywallActionsInterceptor. Type-safe parameters per action, no casting. Available as a member ofPurchaselyin three forms:- reified Kotlin coroutine form:
Purchasely.interceptAction<PLYPresentationAction.Purchase> { info, action -> … }(resolves from thePurchaselyimport — 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).
- reified Kotlin coroutine form:
- Kotlin DSL entrypoint —
Purchasely { 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 viaautomaticDeeplinkHandling). - Independent campaign gating — new
allowCampaignsflag decouples campaign display from deeplink handling. synchronize()completion callbacks —synchronize(onSuccess, onError)for Observer mode; the subscriptions cache is refreshed beforeonSuccessfires.- Structured transition dimensions —
PLYTransitionsupports px (dp) and percentage for drawer/popin height and popin width. A dimension explicitly set to0px/0%is honored as-is; an absent dimension falls back to the surface default (drawer 60%, popin 50%, widthmatch_parent). - Theme mode —
PLYThemeMode.LIGHT/DARK/SYSTEMviasetThemeMode(...)/getThemeMode()at runtime, and (new in 6.0.1) viathemeMode(...)atBuilder/DSL init. - Logging — custom loggers now receive all messages regardless of
logLevel; newlogcatEnabledflag controls Logcat independently.
🔧 Behavioral Fixes
- Observer-mode auto-sync on interceptor
SUCCESS— in Observer mode, the SDK callssynchronize()automatically after a paywall Purchase or Restore action your interceptor handled and reported asSUCCESS, matching iOS. Do not callsynchronize()yourself inside the interceptor — it would double-sync.display(...)then resolves to aPURCHASEDoutcome with the plan (orRESTORED) rather thanCANCELLEDwith a null plan. (MOB-260) open_presentationno longer drops your dismiss callback — when a paywall action navigates to another presentation that has no callback of its own, the originalonDismissed/ close callback is preserved instead of being overwritten by a no-op, so a latercloseAllScreens()correctly reaches your handler. The secondary screen also reaches a terminalDismissedstate so observers no longer block indefinitely.- Default presentation dismiss handler fires for deeplinks —
setDefaultPresentationDismissHandler(...)now delivers a fullPLYPresentationOutcome(withpresentationpopulated) 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 withsafe_area_top), so it no longer stacks with an unconditional inset. Layouts that need bottom spacing must setsafe_area_bottomon 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 (
id→in, 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(wasFull). Add.runningMode(PLYRunningMode.Full)if you want Purchasely to handle/validate purchases. In Observer mode, presentations no longer auto-close after purchase/restore. PLYRunningMode.PaywallObserver→PLYRunningMode.Observer(rename).apiKeyis validated atstart()— null/blank leaves the SDK inert and firesPLYError.Configuration.- Init callback signature simplified —
start { error -> }(dropped the leadingBoolean). Java:Function2<Boolean,PLYError,Unit>→Function1<PLYError,Unit>.
Action interceptor
- Removed
setPaywallActionsInterceptor(),PLYPresentationInfo,PLYPaywallActionHandler/PLYCompletionHandler,PLYPaywallActionListener/PLYProcessActionListener. PLYPresentationActionis now a sealed class (was enum);PLYPresentationActionParametersremoved (params live on each subclass).processAction(false/true)→PLYInterceptResult.SUCCESS/NOT_HANDLED(+ newFAILED).- Kotlin reified
interceptAction<T>requires consumerjvmTarget = 11(or use theClass-based overload).
Presentation API
- All presentation types moved to
io.purchasely.ext.presentation.*(import-only change). PLYPresentationPropertiesremoved — configure via the builder/DSL.Purchasely.presentationView(...)removed →PLYPresentation { … }.preload { … }.PLYPresentation.id→screenId(alsotoMap()key"id"→"screenId").- Suspend
display()extension removed —Loaded.display()is now non-suspend. onClose→onCloseRequested;PLYPresentationClose→PLYPresentationCloseRequested.- Display callbacks now receive a single
PLYPresentationOutcome(carriespurchaseResult,plan,closeReason,error) instead of(result, plan)+ separate error. back()/close()are now onPLYPresentation(Loaded) only.setDefaultPresentationResultHandler→setDefaultPresentationDismissHandler(rename; delivers a fullPLYPresentationOutcome). The analytics presence marker is renamed accordingly (DEFAULT_PRESENTATION_RESULT_HANDLER→DEFAULT_PRESENTATION_DISMISSED_HANDLER).PLYProductViewResultdeprecated →PLYPurchaseResult.
Removed surfaces
- Subscription list & cancellation survey UI fully removed —
subscriptionsFragment(), allPLYSubscription*/cancellation fragments & views, theply/subscriptions&ply/cancellation_surveydeeplinks, and 5 relatedPLYEventsubclasses. Build custom UI fromuserSubscriptions()/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 history —
purchaseHistory()andisPastSubscriber()removed →userSubscriptionsHistory(). PLYPlanintro*methods removed →offer*equivalents (direct rename).PLYPlanTagsINTRO_*/TRIAL_*removed →OFFER_*.
Behavioral changes
allowDeeplinknow defaults totrue(wasfalse). Set.allowDeeplink(false)to keep v5 deferred behavior. Preview deeplinks (?preview=1) always display immediately.- Storeless errors — purchase/restore now return
PLYError.NoStoreConfigured(wasPLYError.Unknown"No store found"). - User attribute mutators return
Deferred<Boolean>(setUserAttribute,clearUserAttribute,increment/decrementUserAttribute, etc.) — safe to ignore.
🛠 Build Requirements
- compileSdk
36(Android 16), targetSdk35(no runtime behavior change), minSdk23. - Kotlin
2.3.21toolchain — picked up via AGP 9's built-in Kotlin. The published modules pinlanguageVersionto 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-serialization1.11.0, Dokka2.2.0.
📖 Full migration guide
https://docs.purchasely.com/docs/migrating-from-v5-to-v6-android