If I rely on Transaction.currentEntitlements
to check if "Unlock more" was purchased, there is a small delay. This is noticeable when changing colors, e.g. .environment(\.colorScheme, store.isUnlocked ? .dark : .light)
. (this is in development mode, maybe it's faster once in production)
@MainActor
func updatePurchasedProducts() async {
var purchasedProducts: [Product] = []
for await result in Transaction.currentEntitlements {
do {
let transaction = try checkVerified(result)
if let product = products.first(where: { $0.id == transaction.productID}) {
purchasedProducts.append(product)
}
} catch {
print("Transaction failed verification", error)
}
}
self.purchasedProducts = purchasedProducts
}
func isPurchased(productIdentifier: String) -> Bool {
return purchasedProducts.contains { product in
product.id == productIdentifier
}
}
Is it common practice to store var purchasedProducts: [Product]
in @AppStorage
?
Full code:
import Foundation
import StoreKit
let unlockMore = "bundleId.unlock_more"
class Store: ObservableObject {
@Published var products: [Product] = []
@Published var purchasedProducts: [Product] = []
var updateListenerTask: Task<Void, Error>? = nil
var isUnlocked: Bool {
get {
return isPurchased(productIdentifier: unlockMore)
}
}
init() {
updateListenerTask = listenForTransations()
Task {
await requestProducts()
await updatePurchasedProducts()
}
}
func listenForTransations() -> Task<Void, Error> {
return Task.detached {
for await result in Transaction.updates {
do {
let transaction = try self.checkVerified(result)
await self.updatePurchasedProducts()
await transaction.finish()
} catch {
print(error)
}
}
}
}
@MainActor
func requestProducts() async {
do {
let productIdentifiers = [unlockMore]
products = try await Product.products(for: productIdentifiers)
} catch {
print(error)
}
}
func purchase(product: Product) async throws -> Transaction? {
let result = try await product.purchase()
switch result {
case .success(let verificationResult):
let transaction = try checkVerified(verificationResult)
await updatePurchasedProducts()
await transaction.finish()
return transaction
case .userCancelled, .pending:
return nil
default:
return nil
}
}
func isPurchased(product: Product) -> Bool {
return purchasedProducts.contains(product)
}
func isPurchased(productIdentifier: String) -> Bool {
return purchasedProducts.contains { product in
product.id == productIdentifier
}
}
@MainActor
func updatePurchasedProducts() async {
var purchasedProducts: [Product] = []
for await result in Transaction.currentEntitlements {
do {
let transaction = try checkVerified(result)
if let product = products.first(where: { $0.id == transaction.productID}) {
purchasedProducts.append(product)
}
} catch {
print("Transaction failed verification", error)
}
}
self.purchasedProducts = purchasedProducts
}
func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
switch result {
case .unverified:
// failed verification
throw StoreError.failedVerification
case .verified(let signedType):
return signedType
}
}
}
enum StoreError: Error {
case failedVerification
}