Purchases.customerInfo(): added overload with a new CacheFetchPolicy#1608
Conversation
ace4e4d to
673df33
Compare
CacheFetchPolicy to allow specifying CustomerInfoManager fetching behaviorPurchases.customerInfo(): added overload with a new CacheFetchPolicy
a588661 to
28dad09
Compare
There was a problem hiding this comment.
Feedback welcome on these names.
There was a problem hiding this comment.
I think I covered all scenarios, but let me know if I missed any or if it'd be worth adding any more.
There was a problem hiding this comment.
I changed all these calls to explicitly pass the previous behavior. But now that we have this feature, it's possible that we might want to customize it and use a different one.
There was a problem hiding this comment.
The new error infrastructure makes this so easy 🎉
65a091e to
ff25c58
Compare
|
looking great so far! and I ❤️ having the options. I left a comment regarding the current behavior |
0013e41 to
d640269
Compare
There was a problem hiding this comment.
BackendErrorCode directly.
There was a problem hiding this comment.
This behavior makes a lot more sense. I wonder if we should change some calls to use this instead now.
There was a problem hiding this comment.
I agree. Let's run it by the support team, they might have thoughts based on customer feedback.
I think we can merge this PR as is (with the current behavior), then ask the support team (and broader team) for feedback regarding the proposed change, then if folks agree make a release that makes this the default.
We'd have to also make a purchases-android equivalent release so that we don't have inconsistent behavior in the hybrids.
There was a problem hiding this comment.
Hmm I have a concern about this fetch policy... If the cache is stale and we try to fetch while offline with this cache policy, we won't use the cached version even if it's stale right? I guess we should add a completion on the fetch so if the fetch fails, to use the cache if it exists even if it's stale. Thoughts?
There was a problem hiding this comment.
🤔 If I called something that said "not stale" I'd expect it to never give me something potentially stale, so if it can't fetch, I think an error might be more appropriate than returning a stale cache for this one
There was a problem hiding this comment.
Well, I can see your reasoning... I think this is ok as long as this option is not the default... But if we wanted to make this the default as was mentioned on this comment, we probably want to be more lenient and allow to use the stale data... I'm sure there are a ton of people that would use the apps while offline that would cause this to error out even if we had a cached value that the customer can potentially use.
There was a problem hiding this comment.
that's a valid point. and also an argument for a "best effort" case, where if the cache is stale, we try to fetch, but if there are any problems we return the cached value.
@NachoSoto thoughts?
There was a problem hiding this comment.
Well, I can see your reasoning... I think this is ok as long as this option is not the default... But if we wanted to make this the default as was mentioned on this comment, we probably want to be more lenient and allow to use the stale data...
Totally agreed.
So we want this to be notStaleCachedOrFetchedUnlessThatFailsInWhichCaseStaleCache. I think this is pointing to the fact that really there's a lot of different combinations, and we can't offer them all. So maybe the right answer is to make sure this is documented correctly (could add a note to this policy).
To create that behavior users could do it themselves. I think it's best if we provide the primitives that users can compose around:
let customerInfo: CustomerInfo?
do {
customerInfo = try await purchases.customerInfo(fetchPolicy: .notStaleCachedOrFetched)
} catch {
customerInfo = try? await purchases.customerInfo(fetchPolicy: .fromCacheOnly)
}What do you guys think?
There was a problem hiding this comment.
Yeah, I think with the current options, users have a lot of flexibility, so as long as it's well documented, customers should be able to do what they need. However, as I mentioned, I wouldn't change the default to notStaleCachedOrFetched since I think that can potentially cause a bunch of issues for users.
We can talk about what the default behavior should be if we think the current behavior (cachedOrFetched) is causing issues
There was a problem hiding this comment.
I added a warning to the docstring: #1708
There was a problem hiding this comment.
All the existing tests are left unchanged, meaning the existing default behavior is still the same.
There was a problem hiding this comment.
I agree. Let's run it by the support team, they might have thoughts based on customer feedback.
I think we can merge this PR as is (with the current behavior), then ask the support team (and broader team) for feedback regarding the proposed change, then if folks agree make a release that makes this the default.
We'd have to also make a purchases-android equivalent release so that we don't have inconsistent behavior in the hybrids.
There was a problem hiding this comment.
the only thing I'd add is to have a case for "returns error if cache was stale and new cache can't be fetched" in case of network errors
There was a problem hiding this comment.
Good idea, added for both .cachedOrFetched and .notStaleCachedOrFetched.
Fixes [CF-19]. This allows specifying `CustomerInfoManager` fetching behavior, both as a public API for users, and also internally. Some future uses of `CustomerInfoManager` might want to ensure that we don't rely on cached data, for example. Thanks to the `APITester` we know that the existing methods still work, this is purely an additive change. This is the core of the change: ```swift /// Specifies the behavior that a caching API should have. @objc(RCCacheFetchPolicy) public enum CacheFetchPolicy: Int { /// Returns values from the cache, or throws an error if not available. case fromCacheOnly /// Always fetch the most up-to-date data. case fetchCurrent /// Default behavior: retruns the cache data if available and not stale, or fetches up-to-date data. case cachedOrFetched /// Default ``CacheFetchPolicy`` behavior. public static let `default`: Self = .cachedOrFetched } ``` The new `CustomerInfoManagerGetCustomerInfoTests` check all the different combinations of fetching behavior. I've taken some of the existing tests and refactored them to be `async`, as well as added a bunch more tests. There is also one big hidden bug fix: `Purchases.getCustomerInfo` now calls `BackendError.asPurchasesError`, so we don't end up returning an internal error type. This was missed because we always return generic `Error` types, so we have no way to know at compile time that we only return `ErrorCode`s.
…on `CacheFetchPolicy.cachedOrFetched`
d640269 to
12ea8c5
Compare
### New Features * `Purchases.customerInfo()`: added overload with a new `CacheFetchPolicy` (#1608) via NachoSoto (@NachoSoto) * `Storefront`: added `sk1CurrentStorefront` for Objective-C (#1614) via NachoSoto (@NachoSoto) ### Bug Fixes * Fix for not being able to read receipts on watchOS (#1625) via Patrick Busch (@patrickbusch) ### Other Changes * Added tests for `PurchasesOrchestrator` invoking `listenForTransactions` only if SK2 is enabled (#1618) via NachoSoto (@NachoSoto) * `PurchasesOrchestrator`: removed `lazy` hack for properties with `@available` (#1596) via NachoSoto (@NachoSoto) * `PurchasesOrchestrator.purchase(sk2Product:promotionalOffer:)`: simplified implementation with new operator (#1602) via NachoSoto (@NachoSoto)
| completion: ((Result<CustomerInfo, BackendError>) -> Void)? | ||
| ) { | ||
| switch fetchPolicy { | ||
| case .fromCacheOnly: |
There was a problem hiding this comment.
I'm working on the android side of this, and one thing I'm wondering is, do we want to fetch new data even with this fetch policy (if stale)? I think that might be better so we update as early as possible, but I could also understand devs thinking that the cache will not be updated in this case... Though if that's the case we should make sure we handle all other possible places where the cache can be updated. cc @NachoSoto @aboedo
There was a problem hiding this comment.
The docs for CachePolicy might help illustrate the desired behavior here.
For this specific case, if there's nothing in cache, we'd return an error right away no fetching. If there's something in cache, it gets returned even if stale. FWIW I think most apps will not want to use this behavior, and will opt for any of the others
There was a problem hiding this comment.
Happy to clarify further if needed!
There was a problem hiding this comment.
I agree that this won't be used much by devs so it's not that big of a deal. The docs don't specify whether this would initiate a fetch, and it doesn't either for some other policies, like the cacheOrFetched policy, it says: Default behavior: returns the cached data if available (even if stale), or fetches up-to-date data., it says or but it can be an and if the cache is stale (I'm probably nitpicking here).
For now I will follow iOS behavior on this, but I'm more concerned about this other question: #1608 (comment) @aboedo
There was a problem hiding this comment.
That is certainly valid feedback. We should update the docs to make it 100% unambiguous, i.e.: specify for each one:
- If cache is available and valid, ...
- If cache is available and stale, ...
- If cache is not available, ...
Where the second part specifies whether an error or value is returned, whether it'll trigger a fetch, and whether it'll wait for the fetch before returning or it'll trigger something in the delegate instead.
Maybe we could specify that in the Android implementation and copy the text back to this one?
…o docstring See conversation in #1608 (comment)
Fixes CF-19.
This allows specifying
CustomerInfoManagerfetching behavior, both as a public API for users, and also internally.Some future uses of
CustomerInfoManagermight want to ensure that we don't rely on cached data, for example.Thanks to the
APITesterwe know that the existing methods still work, this is purely an additive change.This is the core of the change:
The new
CustomerInfoManagerGetCustomerInfoTestscheck all the different combinations of fetching behavior.I've taken some of the existing tests and refactored them to be
async, as well as added a bunch more tests.Purchases.getCustomerInfonow callsBackendError.asPurchasesError, so we don't end up returning an internal error type.This was missed because we always return generic
Errortypes, so we have no way to know at compile time that we only returnErrorCodes.