-
Notifications
You must be signed in to change notification settings - Fork 292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/defer cache updates if woken from push notification #288
Changes from all commits
955b31e
94cd83f
b6528d6
327cc04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,10 +17,13 @@ | |
#define APP_WILL_RESIGN_ACTIVE_NOTIFICATION_NAME NSExtensionHostWillResignActiveNotification | ||
#endif | ||
|
||
#if TARGET_OS_IPHONE | ||
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_MACCATALYST | ||
#import <UIKit/UIKit.h> | ||
#elif TARGET_OS_OSX | ||
#import <AppKit/AppKit.h> | ||
#elif TARGET_OS_WATCH | ||
#import <UIKit/UIKit.h> | ||
#import <WatchKit/WatchKit.h> | ||
#endif | ||
|
||
#if TARGET_OS_MACCATALYST | ||
|
@@ -58,3 +61,11 @@ | |
#else | ||
#define PURCHASES_INITIATED_FROM_APP_STORE_AVAILABLE 0 | ||
#endif | ||
|
||
#if TARGET_OS_IOS || TARGET_OS_TV | ||
#define IS_APPLICATION_BACKGROUNDED UIApplication.sharedApplication.applicationState == UIApplicationStateBackground | ||
#elif TARGET_OS_OSX | ||
#define IS_APPLICATION_BACKGROUNDED NO | ||
#elif TARGET_OS_WATCH | ||
#define IS_APPLICATION_BACKGROUNDED WKExtension.sharedExtension.applicationState == WKApplicationStateBackground | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! Much easier than in Android! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apple's API consistency at its finest 🤪 |
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -290,7 +290,12 @@ - (instancetype)initWithAppUserID:(nullable NSString *)appUserID | |
}; | ||
|
||
[self.identityManager configureWithAppUserID:appUserID]; | ||
[self updateAllCachesWithCompletionBlock:callDelegate]; | ||
if (!self.systemInfo.isApplicationBackgrounded) { | ||
[self updateAllCachesWithCompletionBlock:callDelegate]; | ||
} else { | ||
[self sendCachedPurchaserInfoIfAvailable]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this bit is so that if there's something cached we still call the delegate. I believe this is a departure from Android. @vegaro was there a specific reason not to call the delegate there? I'm worried that not calling it is a bit inconsistent with the philosophy of sending whatever is cached as fast as we can. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, we should be calling in Android. I think that's what I would expect as a developer. |
||
} | ||
|
||
[self configureSubscriberAttributesManager]; | ||
|
||
self.storeKitWrapper.delegate = self; | ||
|
@@ -337,11 +342,8 @@ - (void)setDelegate:(id<RCPurchasesDelegate>)delegate | |
{ | ||
_delegate = delegate; | ||
RCDebugLog(@"Delegate set"); | ||
|
||
RCPurchaserInfo *infoFromCache = [self readPurchaserInfoFromCache]; | ||
if (infoFromCache) { | ||
[self sendUpdatedPurchaserInfoToDelegateIfChanged:infoFromCache]; | ||
} | ||
|
||
[self sendCachedPurchaserInfoIfAvailable]; | ||
} | ||
|
||
#pragma mark - Public Methods | ||
|
@@ -758,6 +760,17 @@ - (void)setPushToken:(nullable NSData *)pushToken { | |
|
||
- (void)applicationDidBecomeActive:(__unused NSNotification *)notif | ||
{ | ||
[self updateAllCachesIfNeeded]; | ||
} | ||
|
||
- (void)sendCachedPurchaserInfoIfAvailable { | ||
RCPurchaserInfo *infoFromCache = [self readPurchaserInfoFromCache]; | ||
if (infoFromCache) { | ||
[self sendUpdatedPurchaserInfoToDelegateIfChanged:infoFromCache]; | ||
} | ||
} | ||
|
||
- (void)updateAllCachesIfNeeded { | ||
RCDebugLog(@"applicationDidBecomeActive"); | ||
if ([self.deviceCache isPurchaserInfoCacheStale]) { | ||
RCDebugLog(@"PurchaserInfo cache is stale, updating caches"); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// | ||
// MockSystemInfo.swift | ||
// PurchasesTests | ||
// | ||
// Created by Andrés Boedo on 7/20/20. | ||
// Copyright © 2020 Purchases. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
class MockSystemInfo: RCSystemInfo { | ||
var stubbedIsApplicationBackgrounded: Bool? | ||
|
||
override func isApplicationBackgrounded() -> Bool { | ||
return stubbedIsApplicationBackgrounded ?? super.isApplicationBackgrounded() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -179,16 +179,16 @@ class PurchasesTests: XCTestCase { | |
let deviceCache = MockDeviceCache() | ||
let subscriberAttributesManager = MockSubscriberAttributesManager() | ||
let identityManager = MockUserManager(mockAppUserID: "app_user"); | ||
|
||
let systemInfo = MockSystemInfo(platformFlavor: nil, platformFlavorVersion: nil, finishTransactions: true) | ||
|
||
let purchasesDelegate = MockPurchasesDelegate() | ||
|
||
var purchases: Purchases! | ||
|
||
func setupPurchases(automaticCollection: Bool = false) { | ||
Purchases.automaticAppleSearchAdsAttributionCollection = automaticCollection | ||
self.identityManager.mockIsAnonymous = false | ||
let systemInfo = RCSystemInfo(platformFlavor: nil, platformFlavorVersion: nil, finishTransactions: true) | ||
|
||
|
||
purchases = Purchases(appUserID: identityManager.currentAppUserID, | ||
requestFetcher: requestFetcher, | ||
receiptFetcher: receiptFetcher, | ||
|
@@ -209,8 +209,7 @@ class PurchasesTests: XCTestCase { | |
func setupAnonPurchases() { | ||
Purchases.automaticAppleSearchAdsAttributionCollection = false | ||
self.identityManager.mockIsAnonymous = true | ||
let systemInfo = RCSystemInfo(platformFlavor: nil, platformFlavorVersion: nil, finishTransactions: true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just cleanup here, this line was duplicated in a couple of places |
||
|
||
|
||
purchases = Purchases(appUserID: nil, | ||
requestFetcher: requestFetcher, | ||
receiptFetcher: receiptFetcher, | ||
|
@@ -259,10 +258,46 @@ class PurchasesTests: XCTestCase { | |
expect(self.purchasesDelegate.purchaserInfoReceivedCount).toEventually(equal(1)) | ||
} | ||
|
||
func testFirstInitializationCallDelegateForAnon() { | ||
setupAnonPurchases() | ||
func testFirstInitializationFromForegroundDelegateForAnonIfNothingCached() { | ||
systemInfo.stubbedIsApplicationBackgrounded = false | ||
setupPurchases() | ||
expect(self.purchasesDelegate.purchaserInfoReceivedCount).toEventually(equal(1)) | ||
} | ||
|
||
func testFirstInitializationFromBackgroundDoesntCallDelegateForAnonIfNothingCached() { | ||
systemInfo.stubbedIsApplicationBackgrounded = true | ||
setupPurchases() | ||
expect(self.purchasesDelegate.purchaserInfoReceivedCount).toEventually(equal(0)) | ||
} | ||
|
||
func testFirstInitializationFromBackgroundDoesntCallDelegateForAnonIfInfoCached() { | ||
systemInfo.stubbedIsApplicationBackgrounded = true | ||
let info = Purchases.PurchaserInfo(data: [ | ||
"subscriber": [ | ||
"subscriptions": [:], | ||
"other_purchases": [:] | ||
]]); | ||
|
||
let jsonObject = info!.jsonObject() | ||
|
||
let object = try! JSONSerialization.data(withJSONObject: jsonObject, options: []); | ||
self.deviceCache.cachedPurchaserInfo[identityManager.currentAppUserID] = object | ||
|
||
setupPurchases() | ||
expect(self.purchasesDelegate.purchaserInfoReceivedCount).toEventually(equal(1)) | ||
} | ||
|
||
func testFirstInitializationFromBackgroundDoesntUpdatePurchaserInfoCache() { | ||
systemInfo.stubbedIsApplicationBackgrounded = true | ||
setupPurchases() | ||
expect(self.backend.getSubscriberCallCount).toEventually(equal(0)) | ||
} | ||
|
||
func testFirstInitializationFromForegroundUpdatesPurchaserInfoCache() { | ||
systemInfo.stubbedIsApplicationBackgrounded = false | ||
setupPurchases() | ||
expect(self.backend.getSubscriberCallCount).toEventually(equal(1)) | ||
} | ||
|
||
func testDelegateIsCalledForRandomPurchaseSuccess() { | ||
setupPurchases() | ||
|
@@ -606,7 +641,7 @@ class PurchasesTests: XCTestCase { | |
} | ||
} | ||
|
||
func testFetchesProductInfoIfNotCached() { | ||
func testFetchesProductInfoIfNotCachedAndAppActive() { | ||
setupPurchases() | ||
let product = MockSKProduct(mockProductIdentifier: "com.product.id1") | ||
|
||
|
@@ -1258,6 +1293,18 @@ class PurchasesTests: XCTestCase { | |
expect(offerings!["base"]!.monthly?.product).toNot(beNil()) | ||
} | ||
|
||
func testFirstInitializationGetsOfferingsIfAppActive() { | ||
systemInfo.stubbedIsApplicationBackgrounded = false | ||
setupPurchases() | ||
expect(self.backend.gotOfferings).toEventually(equal(1)) | ||
} | ||
|
||
func testFirstInitializationDoesntProductInfoFromOfferingsIfAppBackgrounded() { | ||
systemInfo.stubbedIsApplicationBackgrounded = true | ||
setupPurchases() | ||
expect(self.backend.gotOfferings).toEventually(equal(0)) | ||
} | ||
|
||
func testProductInfoIsCachedForOfferings() { | ||
setupPurchases() | ||
expect(self.backend.gotOfferings).toEventually(equal(1)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
watch apps have both