Upgrade Purchasely SDK to 6.0.0#9
Conversation
Migrate the Android integration to the v6 public API per MIGRATION_V6.md:
- Replace the removed global setPaywallActionsInterceptor with per-action
Purchasely.interceptAction<…> suspend handlers; bridge them to the existing
Observer-mode billing flow with a pendingResult continuation that resolves
to PLYInterceptResult.SUCCESS / NOT_HANDLED / FAILED based on the emitted
TransactionResult.
- Switch to allowDeeplink, handleDeeplink, the single-arg start { error -> }
callback, and PLYRunningMode.Observer (was PaywallObserver).
- Move from fetchPresentation/PLYPresentationProperties to the
PLYPresentation { placementId(...) }.preload() DSL and map display() /
buildView() callbacks to PLYPresentationOutcome (PLYPurchaseResult →
DisplayResult).
- Re-anchor presentation imports under io.purchasely.ext.presentation.
Collateral: Purchasely 6.0.0 transitively pulls Google Play Billing 8.x;
update PurchaseManager.queryProductDetailsAsync to the new
QueryProductDetailsResult.productDetailsList shape.
Add mavenLocal() to dependency resolution (remove once 6.0.0 ships to
Maven Central).
Rewrite PurchaselyWrapperTest against the new suspend interceptors and
flow ordering. ./gradlew :app:testDebugUnitTest — 178/178 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AGP 9.2.1 requires Gradle ≥ 9.4.1 (MIGRATION_V6.md §7). The committed wrapper was still 8.13, which broke CI even though local builds passed because the dev machine had 9.4.1 cached. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ose plugin setup
- Switch Purchasely SDK init from Builder chain to the new `Purchasely { ... }` Kotlin DSL entrypoint (idiomatic v6).
- Drop redundant `kotlin.android` plugin alias: AGP 9.2.1 + `kotlin.plugin.compose` 2.2.10 already register the Kotlin extension (was causing "extension already registered" if re-applied).
- Add `mavenLocal()` to repositories for local SDK snapshot testing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrate the iOS sample to the v6 API surface:
- fluent init `Purchasely.apiKey(...).…start { error in }` (.storeKit1 on develop)
- `.paywallObserver` -> `.observer`, `readyToOpenDeeplink` -> `allowDeeplink`,
`isDeeplinkHandled` -> `handleDeeplink`
- typed `interceptAction(.login/.navigate/.purchase/.restore)` returning PLYInterceptResult
- `fetchPresentation` -> `PLYPresentationBuilder.…preload`, dismissal -> PLYPresentationOutcome
- embedded paywall via `PLYPresentationViewController` + UIViewControllerRepresentable
- local Swift package workaround for the unreleased develop snapshot (no root Package.swift)
- docs/migration_v6.md recap
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
CI note after Android v6 Presentation example update:
|
|
@greptileai review |
Greptile SummaryMigrates both Android and iOS integrations from Purchasely SDK v5 to v6, replacing the removed
Confidence Score: 4/5Safe to merge with one fix — the iOS success payment screen may silently skip subscription refresh in some SDK builds. The Android migration is thorough: interceptors, presentation loading, tests, and the enum rename are all consistent. The iOS side has a gap in ios/Shaker/Purchasely/PurchaselyWrapper.swift — specifically the
|
| Filename | Overview |
|---|---|
| ios/Shaker/Purchasely/PurchaselyWrapper.swift | iOS v6 migration: switches to builder DSL, typed interceptors, and async/await observer flow. loadPresentation defensively re-assigns presentation callbacks after preload, but showSuccessPaymentScreen omits this, leaving refreshAfterSuccessPayment unreachable when builder-seeded callbacks don't fire. |
| android/app/src/main/java/com/purchasely/shaker/purchasely/PurchaselyWrapper.kt | Core v6 migration: replaces setPaywallActionsInterceptor + processAction with typed interceptAction suspend handlers bridged through awaitPendingResult; updates SDK init, deeplink, and presentation APIs. |
| android/app/src/test/java/com/purchasely/shaker/purchasely/PurchaselyWrapperTest.kt | Tests updated to match the new suspend-based interceptor API; async/await-style interceptJob pattern correctly resolves pending results and cleans up after each test. |
| android/app/src/main/java/com/purchasely/shaker/data/purchase/PurchaseManager.kt | Updated queryProductDetailsAsync callback to accept the Billing 8.x QueryProductDetailsResult wrapper and extract productDetailsList from it; minimal and correct. |
| android/app/src/main/java/com/purchasely/shaker/data/PurchaselySdkMode.kt | Renames PAYWALL_OBSERVER to OBSERVER and adds LEGACY_PAYWALL_OBSERVER migration constant; fromStorage correctly handles both old and new storage values. |
| ios/Shaker/Purchasely/EmbeddedScreenBanner.swift | Replaces removed PresentationView property with a UIViewControllerRepresentable wrapper around PLYPresentationViewController; clean and correct. |
| android/settings.gradle.kts | Adds mavenLocal() as a temporary dependency resolver for the pre-release Purchasely 6.0.0 SDK; must be removed before merging (explicitly tracked in PR checklist). |
Sequence Diagram
sequenceDiagram
participant SDK as Purchasely SDK v6
participant IW as interceptAction lambda
participant PW as PurchaselyWrapper
participant PM as PurchaseManager
participant App as App / ViewModel
Note over SDK,App: Observer-mode purchase flow (v6)
SDK->>IW: intercept(.purchase) [suspend]
IW->>PW: handlePurchase(info, action)
PW->>PW: awaitPendingResult
PW->>PM: purchaseRequests.emit(PurchaseRequest)
PM->>PM: Google Play Billing purchase
PM->>PW: "transactionResult.emit(Success|Cancelled|Error)"
PW->>PW: pendingResult.invoke(PLYInterceptResult)
PW-->>IW: PLYInterceptResult (resume continuation)
IW-->>SDK: return result
Note over SDK,App: Presentation display flow (v6)
App->>PW: loadPresentation(placementId)
PW->>SDK: "PLYPresentation{}.preload()"
SDK-->>PW: PLYPresentation
PW->>PW: re-assign onClose/onDismissed defensively
PW-->>App: FetchResult.Success(handle)
App->>PW: display(handle, activity)
SDK-->>PW: PLYPresentationOutcome
PW->>PW: toDisplayResult()
alt pendingSuccessfulPurchase
PW->>PW: showSuccessPaymentScreen()
end
PW-->>App: DisplayResult
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
ios/Shaker/Purchasely/PurchaselyWrapper.swift:351-376
**`showSuccessPaymentScreen` skips the defensive callback re-assignment done in `loadPresentation`**
`loadPresentation` explicitly re-assigns `presentation.onClose` and `presentation.onDismissed` on the loaded presentation object after `preload`, with the comment "builder-seeded callbacks are not fired by all develop snapshots." `showSuccessPaymentScreen` only attaches `onDismissed` on the builder; if the SDK snapshot in use doesn't propagate builder callbacks to the loaded presentation, the `refreshAfterSuccessPayment` call is never reached — subscriptions remain stale after the success screen is dismissed. The defensive re-assignment of `presentation.onDismissed` should be added inside the `preload` closure here, mirroring the pattern in `loadPresentation`.
Reviews (3): Last reviewed commit: "fix: block SDK default flow on Observer-..." | Re-trigger Greptile
There was a problem hiding this comment.
Pull request overview
This PR migrates Shaker’s Android + iOS Purchasely integrations to Purchasely SDK v6, updating running modes, deeplink handling, action interception, and presentation fetching/display APIs, plus adjusting Android Billing for the transitive Billing v8 upgrade.
Changes:
- Android: upgrade Purchasely to
6.0.0, migrate to v6 Presentation APIs + typed interceptors, and adapt BillingqueryProductDetailsAsyncusage. - iOS: migrate Purchasely initialization + deeplink + presentation flows to v6 APIs, and introduce a local Swift package wrapper for Purchasely sources.
- Tooling/docs: add temporary
mavenLocal()resolution, update Gradle wrapper, and add a migration write-up.
Reviewed changes
Copilot reviewed 34 out of 34 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| settings.gradle.kts | Adds mavenLocal() to resolve Purchasely v6 artifacts temporarily. |
| android/settings.gradle.kts | Adds mavenLocal() for Android-only build resolution. |
| android/gradle/wrapper/gradle-wrapper.properties | Updates Gradle wrapper distribution URL. |
| android/gradle/libs.versions.toml | Bumps Purchasely + Billing versions and adjusts test dependency versions. |
| android/build.gradle.kts | Updates plugin aliases applied across Android build. |
| android/app/build.gradle.kts | Updates app module plugins and test dependencies; removes explicit Kotlin options. |
| android/app/src/main/java/com/purchasely/shaker/purchasely/PurchaselyWrapper.kt | Migrates to v6 DSL init, typed interceptors, Presentation preload/display APIs, and updated callback types. |
| android/app/src/main/java/com/purchasely/shaker/purchasely/PresentationHandle.kt | Re-anchors PLYPresentation import to v6 presentation package. |
| android/app/src/main/java/com/purchasely/shaker/data/RunningModeRepository.kt | Updates observer mode mapping for v6 running mode naming. |
| android/app/src/main/java/com/purchasely/shaker/data/PurchaselySdkMode.kt | Renames/migrates SDK mode storage/labels to “Observer/Presentation Observer”. |
| android/app/src/main/java/com/purchasely/shaker/data/purchase/PurchaseManager.kt | Adapts to new Billing v8 QueryProductDetailsResult.productDetailsList. |
| android/app/src/main/java/com/purchasely/shaker/ui/screen/settings/SettingsViewModel.kt | Renames paywall flows to presentation flows for onboarding placement. |
| android/app/src/main/java/com/purchasely/shaker/ui/screen/settings/SettingsScreen.kt | Updates UI wiring to new presentation display flows and observer naming. |
| android/app/src/main/java/com/purchasely/shaker/ui/screen/home/HomeViewModel.kt | Renames paywall triggers/callbacks to presentation equivalents. |
| android/app/src/main/java/com/purchasely/shaker/ui/screen/home/HomeScreen.kt | Updates presentation collection + dismissal callback wiring. |
| android/app/src/main/java/com/purchasely/shaker/ui/screen/favorites/FavoritesViewModel.kt | Renames paywall triggers/callbacks to presentation equivalents. |
| android/app/src/main/java/com/purchasely/shaker/ui/screen/favorites/FavoritesScreen.kt | Updates presentation collection + unlock action wiring. |
| android/app/src/main/java/com/purchasely/shaker/ui/screen/detail/DetailViewModel.kt | Renames paywall triggers/callbacks to presentation equivalents; updates comments. |
| android/app/src/main/java/com/purchasely/shaker/ui/screen/detail/DetailScreen.kt | Updates presentation collection + unlock action wiring. |
| android/app/src/test/java/com/purchasely/shaker/purchasely/PurchaselyWrapperTest.kt | Updates tests for v6 interceptor + presentation types and new suspend-based interception model. |
| android/app/src/test/java/com/purchasely/shaker/data/RunningModeRepositoryTest.kt | Updates tests for v6 observer mode mapping. |
| android/app/src/test/java/com/purchasely/shaker/data/PurchaselySdkModeTest.kt | Updates tests for v6 running mode mapping. |
| ios/Shaker/Screens/Settings/SettingsViewModel.swift | Updates stored running mode from .paywallObserver to .observer. |
| ios/Shaker/Screens/Onboarding/OnboardingScreen.swift | Avoids fetching onboarding presentation if onboarding is already completed. |
| ios/Shaker/Purchasely/PurchaselyWrapper.swift | Migrates initialization, deeplinks, action interceptors, and presentation preload/display to v6 APIs. |
| ios/Shaker/Purchasely/EmbeddedScreenBanner.swift | Replaces removed embedded view property with a UIViewControllerRepresentable wrapper. |
| ios/Shaker/Data/RunningModeRepository.swift | Updates stored running mode mapping to .observer. |
| ios/Shaker/Data/PurchaselySDKMode.swift | Maps app “paywall observer” selection to v6 .observer running mode. |
| ios/project.yml | Switches Purchasely dependency to local package and adjusts test strict concurrency setting. |
| ios/LocalPackages/Purchasely/Package.swift | Adds local SPM package wrapper definition for Purchasely sources. |
| ios/LocalPackages/Purchasely/Shims/Exports.swift | Adds exported imports shim for Purchasely sources in SPM. |
| ios/LocalPackages/Purchasely/Shims/PLYLottieView.swift | Adds Swift shim for PLYLottieView to avoid mixed ObjC/Swift target issues in SwiftPM. |
| docs/migration_v6.md | Adds a repo-specific migration guide for Purchasely v6 (Android + iOS). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- PurchaselySdkModeTest/SettingsViewModelTest: replace removed PAYWALL_OBSERVER enum refs with OBSERVER and fix storageValue/label assertions (compile error) - RunningModeRepositoryTest: assert persisted value is "observer" not "paywallObserver" - PurchaselySdkMode: replace "pay" + "wallObserver" split with named LEGACY_PAYWALL_OBSERVER constant - RunningModeRepository: drop dead LEGACY_OBSERVER branch (fromStorage handles it) - PurchaselyWrapper (Android+iOS): return SUCCESS instead of NOT_HANDLED for Observer-mode cancellation/ignored actions so the SDK does not run its own flow
…ptors) Return SUCCESS instead of NOT_HANDLED for Observer-mode purchase/restore cancellations and ignored duplicate actions, so the SDK does not run its own purchase/restore flow (the v6 equivalent of v5 processAction(false)).
|
Addressed the review findings across
Additional Copilot findings also addressed inline:
Generated by Claude Code |
|
@greptileai review Generated by Claude Code |
|
@greptileai review |
…SS on cancel) Complements 97d74a9 (which only fixed PAYWALL_OBSERVER refs) and f801a15: - Detail/Favorites/Home/Settings ViewModelTest: show*Paywall -> show*Presentation, onPaywallDismissed -> onPresentationDismissed (remaining compile errors) - DetailViewModelTest: named args for loadPresentation (v6 inserts screenId at position 1, shifting contentId) - PurchaselyWrapperTest: Cancelled now resolves with SUCCESS, matching the v6 Observer-mode block of the SDK default flow introduced in f801a15 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… work Opening the repo root in Android Studio uses the root Gradle build, which had drifted from the canonical android/ build during the v6/AGP 9 migration: - build.gradle.kts referenced libs.plugins.kotlin.android, an alias the version catalog no longer defines -> sync failed with "Unresolved reference 'android'". - gradle.properties carried AGP-9 opt-out flags absent from android/gradle.properties, notably android.builtInKotlin=false, which disabled AGP's built-in Kotlin support. With no kotlin-android plugin applied, no compile*Kotlin tasks were created: assembleDebug produced an APK without compiling Kotlin and testDebugUnitTest reported NO-SOURCE. Aligns both root files with the proven android/ configuration. Verified from the root: sync (help) OK, compileDebugKotlin present, assembleDebug compiles Kotlin, and testDebugUnitTest runs 180 tests with 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Migrate the Android integration to Purchasely SDK v6 per
MIGRATION_V6.md.setPaywallActionsInterceptorwith per-actionPurchasely.interceptAction<…>suspend handlers; bridge them to the existing Observer-mode billing flow via apendingResultcontinuation resolved toPLYInterceptResult.{SUCCESS,NOT_HANDLED,FAILED}based onTransactionResult.allowDeeplink,handleDeeplink, the single-argstart { error -> }callback, andPLYRunningMode.Observer(wasPaywallObserver).fetchPresentation/PLYPresentationPropertiesto thePLYPresentation { placementId(...) }.preload()DSL and mapdisplay()/buildView()callbacks toPLYPresentationOutcome(PLYPurchaseResult→DisplayResult).io.purchasely.ext.presentation.Collateral: Purchasely 6.0.0 transitively pulls Google Play Billing 8.x; updated
PurchaseManager.queryProductDetailsAsyncto the newQueryProductDetailsResult.productDetailsListshape.mavenLocal()is temporarily added to dependency resolution — remove once 6.0.0 ships to Maven Central.Test plan
./gradlew :app:testDebugUnitTest— 178/178 passing locally against apublishToMavenLocalbuild ofio.purchasely:core:6.0.0+io.purchasely:google-play:6.0.0.handleDeeplink.mavenLocal()fromsettings.gradle.ktsonce 6.0.0 is on Maven Central, and re-run the test suite.🤖 Generated with Claude Code
Android v6 Presentation coverage added
onPresented,onCloseRequested, runtime colors, UI flags,screenId,flowId, and prepareddisplay(...).cd android && ./gradlew :app:assembleDebug --quiet.