Skip to content

Commit

Permalink
Prevent paywall PurchaseHandler from being cleared on rerender (#4035)
Browse files Browse the repository at this point in the history
### Motivation

Fixes #4034

`PurchaseHandler` was being cleared/reset when a paywall was being
rendered (ex: if an overlay was applied to it)

### Description

- Restores `PurchaseHandler` back to being a `@StateObject` from #3187
- `checkForConfigurationConsistency()` has a few changes
- Takes a `PurchaseHandler` as a parameter so it doesn't try to access
the `StateObject`
- This was fixes the purple Xcode warning of being access wrongly by it
being a `StateObject` again
- Removes `mutating` keyword and returns an `NSError?` which it now sets
in the constructor

### Demo

#### Using RevenueCat


https://github.com/RevenueCat/purchases-ios/assets/401294/7e6430ca-6243-49ad-b7c0-8a90d90894f8

#### Using MyApp


https://github.com/RevenueCat/purchases-ios/assets/401294/829f12c3-a11a-4952-883c-7ae7addd9c1d
  • Loading branch information
joshdholtz authored Jul 10, 2024
1 parent 45d2cb7 commit dbcfe0c
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 30 deletions.
4 changes: 2 additions & 2 deletions RevenueCatUI/Data/PaywallViewConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct PaywallViewConfiguration {
var fonts: PaywallFontProvider
var displayCloseButton: Bool
var introEligibility: TrialOrIntroEligibilityChecker?
var purchaseHandler: PurchaseHandler
var purchaseHandler: PurchaseHandler?

init(
content: Content,
Expand All @@ -29,7 +29,7 @@ struct PaywallViewConfiguration {
fonts: PaywallFontProvider = DefaultPaywallFontProvider(),
displayCloseButton: Bool = false,
introEligibility: TrialOrIntroEligibilityChecker? = nil,
purchaseHandler: PurchaseHandler
purchaseHandler: PurchaseHandler? = nil
) {
self.content = content
self.customerInfo = customerInfo
Expand Down
43 changes: 17 additions & 26 deletions RevenueCatUI/PaywallView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,7 @@ public struct PaywallView: View {
@Environment(\.locale)
private var locale

// Do NOT reference this object directly, always use `purchaseHandler`.
// `doNotAccessPurchaseHandler` is here to own the `PurchaseHandler` when
// it is owned by this view. When it is owned by an external object, this
// references a "dummy" purchase handler.
@StateObject
private var doNotAccessPurchaseHandler: PurchaseHandler

@ObservedObject
private var purchaseHandler: PurchaseHandler

@StateObject
Expand Down Expand Up @@ -110,16 +103,9 @@ public struct PaywallView: View {
}

// @PublicForExternalTesting
init(configuration: PaywallViewConfiguration, paywallViewOwnsPurchaseHandler: Bool = true) {
if paywallViewOwnsPurchaseHandler {
self._doNotAccessPurchaseHandler = .init(wrappedValue: configuration.purchaseHandler)
} else {
// this is unused and is only present to fulfill the need to have an object assigned
// to a @StateObject
self._doNotAccessPurchaseHandler = .init(wrappedValue: PurchaseHandler.default())
}

self.purchaseHandler = configuration.purchaseHandler
init(configuration: PaywallViewConfiguration) {
let purchaseHandler = configuration.purchaseHandler ?? .default()
self._purchaseHandler = .init(wrappedValue: purchaseHandler)

self._introEligibility = .init(wrappedValue: configuration.introEligibility ?? .default())

Expand All @@ -135,31 +121,36 @@ public struct PaywallView: View {
self.fonts = configuration.fonts
self.displayCloseButton = configuration.displayCloseButton

checkForConfigurationConsistency()
self.initializationError = Self.checkForConfigurationConsistency(purchaseHandler: purchaseHandler)
}

private mutating func checkForConfigurationConsistency() {
switch self.purchaseHandler.purchasesAreCompletedBy {
private static func checkForConfigurationConsistency(purchaseHandler: PurchaseHandler) -> NSError? {
switch purchaseHandler.purchasesAreCompletedBy {
case .myApp:
if self.purchaseHandler.performPurchase == nil || self.purchaseHandler.performRestore == nil {
if purchaseHandler.performPurchase == nil || purchaseHandler.performRestore == nil {
let missingBlocks: String
if self.purchaseHandler.performPurchase == nil && self.purchaseHandler.performRestore == nil {
if purchaseHandler.performPurchase == nil && purchaseHandler.performRestore == nil {
missingBlocks = "performPurchase and performRestore are"
} else if self.purchaseHandler.performPurchase == nil {
} else if purchaseHandler.performPurchase == nil {
missingBlocks = "performPurchase is"
} else {
missingBlocks = "performRestore is"
}

let error = PaywallError.performPurchaseAndRestoreHandlersNotDefined(missingBlocks: missingBlocks)
self.initializationError = error as NSError
let error = PaywallError.performPurchaseAndRestoreHandlersNotDefined(
missingBlocks: missingBlocks
) as NSError
Logger.error(error)

return error
}
case .revenueCat:
if self.purchaseHandler.performPurchase != nil || self.purchaseHandler.performRestore != nil {
if purchaseHandler.performPurchase != nil || purchaseHandler.performRestore != nil {
Logger.warning(PaywallError.purchaseAndRestoreDefinedForRevenueCat)
}
}

return nil
}

// swiftlint:disable:next missing_docs
Expand Down
3 changes: 1 addition & 2 deletions RevenueCatUI/View+PresentPaywall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -493,8 +493,7 @@ private struct PresentingPaywallModifier: ViewModifier {
displayCloseButton: true,
introEligibility: self.introEligibility,
purchaseHandler: self.purchaseHandler
),
paywallViewOwnsPurchaseHandler: false
)
)
.onPurchaseStarted {
self.purchaseStarted?($0)
Expand Down

0 comments on commit dbcfe0c

Please sign in to comment.