This is a draft release for review.
We need to consider how we launch a beta via cocoapods with a name change from Paywall to SuperwallKit.
Areas to look out for:
Identity management.
Paywall presentation flow.
Events (both in app and the interaction with the backend).
Subscription controller - i.e. superwall handling purchasing vs the delegate. What happens if someone wants a non-subscription based product?
Overall internal structure.
Ease of transition between Paywall -> SuperwallKit. There are probably areas where we can guide people via the Graveyard folder. And also just looking out for any errors on the first run from using Paywall to having just upgraded to SuperwallKit.
In-app Documentation.
Online SDK docs.
Example apps.
Overall API design.
Future considerations for the SDK.
Thread safety and performance.
Purchasing using SK2 in the delegate.
Objective-C compatibility
The following is from the changelog for v3.0. Not all changes are in the changelog (as its consumer facing), but it gives a good summary:
Breaking Changes
Renames the package from Paywall to SuperwallKit.
Renames the primary static class for integrating Superwall from Paywall to Superwall.
Sets the minimum iOS version to iOS 13.
Renames preloadPaywalls(forTriggers:) to preloadPaywalls(forEvents:)
Renames configure(apiKey:userId:delegate:options:) to configure(apiKey:delegate:options:). This means you no longer provide a userId with configure. Instead you must use the new identity API detailed below.
Changes PaywallOptions to SuperwallOptions. This now clearly defines which of the options are explicit to paywalls vs other configuration options within the SDK.
Renames Superwall.trigger(event:) to Superwall.track(event:). We found that having separate implicit (Superwall.track(event:)) and explicit (Superwall.trigger(event:)) trigger functions caused confusion. So from now on, you'll just use Superwall.track(event:) for all events within your app.
Renames Paywall.EventName to SuperwallEvent and removes .manualPresent as a SuperwallEvent.
Renames PaywallDelegate to SuperwallDelegate.
Superwall automatically handles all subscription-related logic, meaning that it's no longer a requirement to implement any of the delegate methods. Note that if you're using RevenueCat, you will still need to use the delegate methods. This is because the Superwall-handled subscription status is App Store account-specific, whereas RevenueCat is logged in user-specific. If this isn't a problem, you can just set RevenueCat in observer mode and we'll take care of the rest :)
Moves purchasing logic from the delegate into a protocol called SubscriptionController. You return your SubscriptionController from the delegate method subscriptionController().
For Swift users, this changes the SubscriptionController method purchase(product:) to an async function that returns a PurchaseResult. Here, you need to return the result of the user attempting to purchase a product, making sure you handle all cases of PurchaseResult: .purchased, .cancelled, .pending, failed(Error).
For Objective-C users, this changes the delegate method purchase(product:) to purchase(product:completion:). You call the completion block with the result of the user attempting to purchase a product, making sure you handle all cases of PurchaseResultObjc: .purchased, .cancelled, .pending, failed. When you have a purchasing error, you need to call the completion block with the .failed case along with the error.
Changes restorePurchases() to an async function that returns a boolean instead of having a completion block.
Removes identify(userId:) in favor of the new Identity API detailed below.
Removes Paywall.load(identifier:). This was being used to preload a paywall by identifier.
Removes .triggerPaywall() for SwiftUI apps. Instead, SwiftUI users should now use the UIKit function Superwall.track(). Take a look at our SwiftUI example app to see how that works.
Enhancements
New identity API:
logIn(userId:): Logs in a user with their userId to retrieve paywalls that they've been assigned to.
createAccount(userId:): Creates an account with Superwall. This links a userId to Superwall's automatically generated alias.
logOut(userId:): Logs out the user, which resets on-device paywall assignments and the userId stored by Superwall.
reset(): Resets the userId, on-device paywall assignments, and data stored by Superwall. This can be called even if the user isn't logged in.
The identity API can be accessed using async/await or completion handlers.
New function Superwall.publisher(forEvent:params:overrides) which returns a PaywallStatePublisher (AnyPublisher<PaywallState, Never>) for those Combine lovers. By subscribing to this publisher, you can receive state updates of your paywall. We've updated our sample apps to show you how to use that.
Adds Superwall.isLoggedIn to check whether the user is logged in to the SDK or not. This will be true if you've previously called logIn(userId:) or createAccount(userId:).
Adds a new example app, UIKit+RevenueCat, which shows you how to use Superwall with RevenueCat.
Adds a new Objective-C example app UIKit-Objc.
Adds an Objective-C-only function removeUserAttributes(_:) to remove user attributes. In Swift, to remove attributes you can pass in nil for a specific attribute in setUserAttributes(_:).
Adds getTrackResult(forEvent:params:). This returns a TrackResult which tells you the result of tracking an event, without actually tracking it. This is useful if you want to figure out whether a paywall will show in the future.
Logs when products fail to load with a link to help diagnose the cause.
Adds a published property hasActiveSubscription, which you can check to determine whether Superwall detects an active subscription. Its value is stored on disk and synced with the active purchases on device. If you're using Combine or SwiftUI, you can subscribe or bind to this to get notified whenever the user's subscription status changes. If you're implementing your own SubscriptionController, you should rely on your own logic to determine subscription status.
Adds a published property isConfigured. This is a boolean which you can use to check whether Superwall is configured and ready to present paywalls.
Adds isFreeTrialAvailable to PaywallInfo.
Fixes
Fixes a caching issue where the paywall was still showing in free trial mode when it shouldn't have. This was happening if you had purchased a free trial, let it expire, then reopened the paywall. Note that in Sandbox environments this issue may still occur due to introductory offers not being added to a receipt until after a purchase.
The API uses background threads wherever possible, dispatching to the main thread only when necessary and when returning from completion blocks.
The API is now fully compatible with Objective-C.
Setting the PaywallOptionautomaticallyDismiss to false now keeps the loading indicator visible after restoring and successfully purchasing until you manually dismiss the paywall.
Improves the speed of requests by changing the cache policy of requests to our servers.
Fixes session_start, app_launch and first_seen not being tracked if the SDK was initialised a few seconds after app launch.
Checklist
[ ] All tests pass. Demo project builds and runs.
[ ] I added tests, an experiment, or detailed why my change isn't tested.
[ ] I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
[ ] I have run swiftlint in the main directory and fixed any issues.
[ ] I have updated the SDK documentation as well as the online docs.
Changes in this pull request
This is a draft release for review. We need to consider how we launch a beta via cocoapods with a name change from Paywall to SuperwallKit.
Areas to look out for:
Graveyard
folder. And also just looking out for any errors on the first run from using Paywall to having just upgraded to SuperwallKit.The following is from the changelog for v3.0. Not all changes are in the changelog (as its consumer facing), but it gives a good summary:
Breaking Changes
Paywall
toSuperwallKit
.Paywall
toSuperwall
.preloadPaywalls(forTriggers:)
topreloadPaywalls(forEvents:)
configure(apiKey:userId:delegate:options:)
toconfigure(apiKey:delegate:options:)
. This means you no longer provide auserId
with configure. Instead you must use the new identity API detailed below.PaywallOptions
toSuperwallOptions
. This now clearly defines which of the options are explicit to paywalls vs other configuration options within the SDK.Superwall.trigger(event:)
toSuperwall.track(event:)
. We found that having separate implicit (Superwall.track(event:)
) and explicit (Superwall.trigger(event:)
) trigger functions caused confusion. So from now on, you'll just useSuperwall.track(event:)
for all events within your app.Paywall.EventName
toSuperwallEvent
and removes.manualPresent
as aSuperwallEvent
.PaywallDelegate
toSuperwallDelegate
.SubscriptionController
. You return yourSubscriptionController
from the delegate methodsubscriptionController()
.SubscriptionController
methodpurchase(product:)
to an async function that returns aPurchaseResult
. Here, you need to return the result of the user attempting to purchase a product, making sure you handle all cases ofPurchaseResult
:.purchased
,.cancelled
,.pending
,failed(Error)
.purchase(product:)
topurchase(product:completion:)
. You call the completion block with the result of the user attempting to purchase a product, making sure you handle all cases ofPurchaseResultObjc
:.purchased
,.cancelled
,.pending
,failed
. When you have a purchasing error, you need to call the completion block with the.failed
case along with the error.restorePurchases()
to an async function that returns a boolean instead of having a completion block.identify(userId:)
in favor of the new Identity API detailed below.Paywall.load(identifier:)
. This was being used to preload a paywall by identifier..triggerPaywall()
for SwiftUI apps. Instead, SwiftUI users should now use the UIKit functionSuperwall.track()
. Take a look at our SwiftUI example app to see how that works.Enhancements
logIn(userId:)
: Logs in a user with theiruserId
to retrieve paywalls that they've been assigned to.createAccount(userId:)
: Creates an account with Superwall. This links auserId
to Superwall's automatically generated alias.logOut(userId:)
: Logs out the user, which resets on-device paywall assignments and theuserId
stored by Superwall.reset()
: Resets theuserId
, on-device paywall assignments, and data stored by Superwall. This can be called even if the user isn't logged in.Superwall.publisher(forEvent:params:overrides)
which returns aPaywallStatePublisher
(AnyPublisher<PaywallState, Never>
) for those Combine lovers. By subscribing to this publisher, you can receive state updates of your paywall. We've updated our sample apps to show you how to use that.Superwall.isLoggedIn
to check whether the user is logged in to the SDK or not. This will be true if you've previously calledlogIn(userId:)
orcreateAccount(userId:)
.removeUserAttributes(_:)
to remove user attributes. In Swift, to remove attributes you can pass innil
for a specific attribute insetUserAttributes(_:)
.getTrackResult(forEvent:params:)
. This returns aTrackResult
which tells you the result of tracking an event, without actually tracking it. This is useful if you want to figure out whether a paywall will show in the future.hasActiveSubscription
, which you can check to determine whether Superwall detects an active subscription. Its value is stored on disk and synced with the active purchases on device. If you're using Combine or SwiftUI, you can subscribe or bind to this to get notified whenever the user's subscription status changes. If you're implementing your ownSubscriptionController
, you should rely on your own logic to determine subscription status.isConfigured
. This is a boolean which you can use to check whether Superwall is configured and ready to present paywalls.isFreeTrialAvailable
toPaywallInfo
.Fixes
PaywallOption
automaticallyDismiss
tofalse
now keeps the loading indicator visible after restoring and successfully purchasing until you manually dismiss the paywall.session_start
,app_launch
andfirst_seen
not being tracked if the SDK was initialised a few seconds after app launch.Checklist
CHANGELOG.md
for any breaking changes, enhancements, or bug fixes.swiftlint
in the main directory and fixed any issues.