Bug in Apple's StoreKit 2 demo code

I’m just documenting this in case it helps someone else.

For WWDC21, Apple introduced a significant update to its StoreKit framework (see presentation and sample code). The demonstration code used SwiftUI and Swift’s new async/await capability. I was working with the code using Xcode’s ability to simulate the AppStore via the Products.storekit file (edit the the build/run scheme to use Products.storekit).

Below is a vide of the bug.

I launch the program from Xcode, buy two items in the store, then stop the program via Xcode. Then I launch the program again via Xcode, return to the store view (which shows the two items I purchased earlier with their green checkmarks). I buy a third item, at which point the green checkmarks next to the previously purchased items disappear and are replaced by the prices (indicating I hadn’t bought them).

There are two issues that cause this incorrect behavior.

First, when the StoreView is displayed, the button uses one logic to determine if a price should be displayed (i.e., the user hasn’t bought the item) or a checkmark (i.e., the user has already bought the item):

.onAppear {
    Task {
        isPurchased = (try? await store.isPurchased(product.id)) ?? false
    }
}

But, when a user purchases an item and the set store.purchaseIdentifiers is updated, the code uses a different instruction to determine if the item was purchased.

.onChange(of: store.purchasedIdentifiers) { identifiers in
    Task {
        isPurchased = identifiers.contains(product.id)
    }
}

Second, when I start the program again, Store’s "isPurchased” set is empty. It isn’t initially populated with the previously purchased items.

Now, when I purchase a new item, the Store’s “purchasedIdentifiers” set is updated (with the just purchased item), triggering the .onChange code block for all the buttons. When the buttons of the previously purchased items check the Store’s purchasedIdentifiers set, their product IDs are not in the set. The code thus thinks the item has not been purchased, and the button switches from a green checked mark to a blue button showing the price.

In other words, on the Store view’s initial display, this code used to determine if a blue button with the item’s price should be displayed or a checkmark should be displayed is:

isPurchased = (try? await store.isPurchased(product.id)) ?? false

But later, when the button item checks itself again after another item has been purchased, it uses this code:

isPurchased = identifiers.contains(product.id)

The simplest solution (although perhaps not the most efficient), is to replace the isPurchased check in the .onChange block with the same code in the .onAppear block.