Skip to content

Commit 2affb24

Browse files
authored
Better completion callback for activate (#5646)
1 parent cc233d6 commit 2affb24

File tree

5 files changed

+127
-32
lines changed

5 files changed

+127
-32
lines changed

FirebaseRemoteConfig/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
# Unreleased
1+
# v4.5.0
22
- [changed] Updated `fetchAndActivateWithCompletionHandler:` implementation to activate asynchronously. (#5617)
33
- [fixed] Remove undefined class via removing unused proto generated source files. (#4334)
44
- [added] Add an URLSession Partial Mock to enable testing without a backend. (#5633)
5+
- [added] Added activate API that returns a callback with an additional `bool` parameter indicating
6+
if the config has changed or not. The new API does not error if the console is unchanged. The old
7+
activate API with only an error parameter is deprecated and will be removed at the next major
8+
release. (#3586)
59

610
# v4.4.11
711
- [fixed] Fixed a bug where settings updates weren't applied before fetches. (#4740)

FirebaseRemoteConfig/Sources/FIRRemoteConfig.m

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -278,18 +278,31 @@ - (BOOL)activateFetched {
278278
return didActivate;
279279
}
280280

281+
typedef void (^FIRRemoteConfigActivateChangeCompletion)(BOOL changed, NSError *_Nullable error);
282+
283+
- (void)activateWithCompletion:(FIRRemoteConfigActivateChangeCompletion)completion {
284+
[self activateWithEitherHandler:completion deprecatedHandler:nil];
285+
}
286+
281287
- (void)activateWithCompletionHandler:(FIRRemoteConfigActivateCompletion)completionHandler {
288+
[self activateWithEitherHandler:nil deprecatedHandler:completionHandler];
289+
}
290+
291+
- (void)activateWithEitherHandler:(FIRRemoteConfigActivateChangeCompletion)completion
292+
deprecatedHandler:(FIRRemoteConfigActivateCompletion)deprecatedHandler {
282293
__weak FIRRemoteConfig *weakSelf = self;
283294
void (^applyBlock)(void) = ^(void) {
284295
FIRRemoteConfig *strongSelf = weakSelf;
285296
if (!strongSelf) {
286297
NSError *error = [NSError errorWithDomain:FIRRemoteConfigErrorDomain
287298
code:FIRRemoteConfigErrorInternalError
288299
userInfo:@{@"ActivationFailureReason" : @"Internal Error."}];
289-
if (completionHandler) {
300+
if (completion) {
290301
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
291-
completionHandler(error);
302+
completion(NO, error);
292303
});
304+
} else if (deprecatedHandler) {
305+
deprecatedHandler(error);
293306
}
294307
FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000068", @"Internal error activating config.");
295308
return;
@@ -298,17 +311,21 @@ - (void)activateWithCompletionHandler:(FIRRemoteConfigActivateCompletion)complet
298311
// ignored.
299312
if (strongSelf->_settings.lastETagUpdateTime == 0 ||
300313
strongSelf->_settings.lastETagUpdateTime <= strongSelf->_settings.lastApplyTimeInterval) {
301-
FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000069",
302-
@"Most recently fetched config is already activated.");
303-
NSError *error = [NSError
304-
errorWithDomain:FIRRemoteConfigErrorDomain
305-
code:FIRRemoteConfigErrorInternalError
306-
userInfo:@{
307-
@"ActivationFailureReason" : @"Most recently fetched config is already activated"
308-
}];
309-
if (completionHandler) {
314+
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069",
315+
@"Most recently fetched config is already activated.");
316+
if (completion) {
310317
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
311-
completionHandler(error);
318+
completion(NO, nil);
319+
});
320+
} else if (deprecatedHandler) {
321+
NSError *error = [NSError
322+
errorWithDomain:FIRRemoteConfigErrorDomain
323+
code:FIRRemoteConfigErrorInternalError
324+
userInfo:@{
325+
@"ActivationFailureReason" : @"Most recently fetched config already activated"
326+
}];
327+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
328+
deprecatedHandler(error);
312329
});
313330
}
314331
return;
@@ -319,9 +336,13 @@ - (void)activateWithCompletionHandler:(FIRRemoteConfigActivateCompletion)complet
319336
[strongSelf updateExperiments];
320337
strongSelf->_settings.lastApplyTimeInterval = [[NSDate date] timeIntervalSince1970];
321338
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated.");
322-
if (completionHandler) {
339+
if (completion) {
340+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
341+
completion(YES, nil);
342+
});
343+
} else if (deprecatedHandler) {
323344
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
324-
completionHandler(nil);
345+
deprecatedHandler(nil);
325346
});
326347
}
327348
};

FirebaseRemoteConfig/Sources/Public/FIRRemoteConfig.h

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -232,18 +232,24 @@ NS_SWIFT_NAME(RemoteConfig)
232232

233233
#pragma mark - Apply
234234

235+
/// Applies Fetched Config data to the Active Config, causing updates to the behavior and appearance
236+
/// of the app to take effect (depending on how config data is used in the app).
237+
/// @param completion Activate operation callback with changed and error parameters.
238+
- (void)activateWithCompletion:(void (^_Nullable)(BOOL changed,
239+
NSError *_Nullable error))completion;
240+
235241
/// Applies Fetched Config data to the Active Config, causing updates to the behavior and appearance
236242
/// of the app to take effect (depending on how config data is used in the app).
237243
/// @param completionHandler Activate operation callback.
238-
- (void)activateWithCompletionHandler:(nullable FIRRemoteConfigActivateCompletion)completionHandler;
244+
- (void)activateWithCompletionHandler:(nullable FIRRemoteConfigActivateCompletion)completionHandler
245+
DEPRECATED_MSG_ATTRIBUTE("Use -[FIRRemoteConfig activateWithCompletion:] instead.");
239246

240247
/// This method is deprecated. Please use -[FIRRemoteConfig activateWithCompletionHandler:] instead.
241248
/// Applies Fetched Config data to the Active Config, causing updates to the behavior and appearance
242249
/// of the app to take effect (depending on how config data is used in the app).
243250
/// Returns true if there was a Fetched Config, and it was activated.
244251
/// Returns false if no Fetched Config was found, or the Fetched Config was already activated.
245-
- (BOOL)activateFetched DEPRECATED_MSG_ATTRIBUTE("Use -[FIRRemoteConfig activate] "
246-
"instead.");
252+
- (BOOL)activateFetched DEPRECATED_MSG_ATTRIBUTE("Use -[FIRRemoteConfig activate] instead.");
247253

248254
#pragma mark - Get Config
249255
/// Enables access to configuration values by using object subscripting syntax.
@@ -265,8 +271,7 @@ NS_SWIFT_NAME(RemoteConfig)
265271
/// @param aNamespace Config results under a given namespace.
266272
- (nonnull FIRRemoteConfigValue *)configValueForKey:(nullable NSString *)key
267273
namespace:(nullable NSString *)aNamespace
268-
DEPRECATED_MSG_ATTRIBUTE("Use -[FIRRemoteConfig configValueForKey:] "
269-
"instead.");
274+
DEPRECATED_MSG_ATTRIBUTE("Use -[FIRRemoteConfig configValueForKey:] instead.");
270275

271276
/// Gets the config value of a given namespace and a given source.
272277
/// @param key Config key.
@@ -281,8 +286,7 @@ NS_SWIFT_NAME(RemoteConfig)
281286
- (nonnull FIRRemoteConfigValue *)configValueForKey:(nullable NSString *)key
282287
namespace:(nullable NSString *)aNamespace
283288
source:(FIRRemoteConfigSource)source
284-
DEPRECATED_MSG_ATTRIBUTE("Use -[FIRRemoteConfig configValueForKey:source:] "
285-
"instead.");
289+
DEPRECATED_MSG_ATTRIBUTE("Use -[FIRRemoteConfig configValueForKey:source:] instead.");
286290

287291
/// Gets all the parameter keys from a given source and a given namespace.
288292
///

FirebaseRemoteConfig/Tests/FakeConsole/FakeConsoleTests.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class FakeConsoleTests: APITestBase {
2323
fakeConsole.config = ["Key1": "Value1"]
2424
}
2525

26+
// Test old API.
2627
// Contrast with testUnchangedActivateWillError in APITests.swift.
2728
func testChangedActivateWillNotError() {
2829
let expectation = self.expectation(description: #function)
@@ -59,6 +60,43 @@ class FakeConsoleTests: APITestBase {
5960
waitForExpectations()
6061
}
6162

63+
// Test New API.
64+
// Contrast with testUnchangedActivateWillFlag in APITests.swift.
65+
func testChangedActivateWillNotFlag() {
66+
let expectation = self.expectation(description: #function)
67+
config.fetch { status, error in
68+
if let error = error {
69+
XCTFail("Fetch Error \(error)")
70+
}
71+
XCTAssertEqual(status, RemoteConfigFetchStatus.success)
72+
self.config.activate { changed, error in
73+
XCTAssertNil(error)
74+
XCTAssertTrue(changed)
75+
XCTAssertEqual(self.config["Key1"].stringValue, "Value1")
76+
expectation.fulfill()
77+
}
78+
}
79+
waitForExpectations()
80+
81+
// Simulate updating console.
82+
fakeConsole.config = ["Key1": "Value2"]
83+
84+
let expectation2 = self.expectation(description: #function + "2")
85+
config.fetch { status, error in
86+
if let error = error {
87+
XCTFail("Fetch Error \(error)")
88+
}
89+
XCTAssertEqual(status, RemoteConfigFetchStatus.success)
90+
self.config.activate { changed, error in
91+
XCTAssertNil(error)
92+
XCTAssert(changed)
93+
XCTAssertEqual(self.config["Key1"].stringValue, "Value2")
94+
expectation2.fulfill()
95+
}
96+
}
97+
waitForExpectations()
98+
}
99+
62100
private func waitForExpectations() {
63101
let kFIRStorageIntegrationTestTimeout = 10.0
64102
waitForExpectations(timeout: kFIRStorageIntegrationTestTimeout,

FirebaseRemoteConfig/Tests/SwiftAPI/APITests.swift

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,8 @@ class APITests: APITestBase {
3232
XCTFail("Fetch Error \(error)")
3333
}
3434
XCTAssertEqual(status, RemoteConfigFetchStatus.success)
35-
self.config.activate { error in
36-
if let error = error {
37-
// This API returns an error if the config was unchanged.
38-
print("Activate Error \(error)")
39-
}
35+
self.config.activate { _, error in
36+
XCTAssertNil(error)
4037
XCTAssertEqual(self.config["Key1"].stringValue, "Value1")
4138
expectation.fulfill()
4239
}
@@ -51,11 +48,8 @@ class APITests: APITestBase {
5148
XCTFail("Fetch Error \(error)")
5249
}
5350
XCTAssertEqual(status, RemoteConfigFetchStatus.success)
54-
self.config.activate { error in
55-
if let error = error {
56-
// This API returns an error if the config was unchanged.
57-
print("Activate Error \(error)")
58-
}
51+
self.config.activate { _, error in
52+
XCTAssertNil(error)
5953
XCTAssertEqual(self.config["Key1"].stringValue, "Value1")
6054
expectation.fulfill()
6155
}
@@ -75,6 +69,7 @@ class APITests: APITestBase {
7569
waitForExpectations()
7670
}
7771

72+
// Test old API.
7873
// Contrast with testChangedActivateWillNotError in FakeConsole.swift.
7974
func testUnchangedActivateWillError() {
8075
let expectation = self.expectation(description: #function)
@@ -110,6 +105,39 @@ class APITests: APITestBase {
110105
waitForExpectations()
111106
}
112107

108+
// Test New API.
109+
// Contrast with testChangedActivateWillNotFlag in FakeConsole.swift.
110+
func testUnchangedActivateWillFlag() {
111+
let expectation = self.expectation(description: #function)
112+
config.fetch { status, error in
113+
if let error = error {
114+
XCTFail("Fetch Error \(error)")
115+
}
116+
XCTAssertEqual(status, RemoteConfigFetchStatus.success)
117+
self.config.activate { changed, error in
118+
XCTAssertTrue(!APITests.useFakeConfig || changed)
119+
XCTAssertNil(error)
120+
XCTAssertEqual(self.config["Key1"].stringValue, "Value1")
121+
expectation.fulfill()
122+
}
123+
}
124+
waitForExpectations()
125+
let expectation2 = self.expectation(description: #function + "2")
126+
config.fetch { status, error in
127+
if let error = error {
128+
XCTFail("Fetch Error \(error)")
129+
}
130+
XCTAssertEqual(status, RemoteConfigFetchStatus.success)
131+
self.config.activate { changed, error in
132+
XCTAssertFalse(changed)
133+
XCTAssertNil(error)
134+
XCTAssertEqual(self.config["Key1"].stringValue, "Value1")
135+
expectation2.fulfill()
136+
}
137+
}
138+
waitForExpectations()
139+
}
140+
113141
private func waitForExpectations() {
114142
let kFIRStorageIntegrationTestTimeout = 10.0
115143
waitForExpectations(timeout: kFIRStorageIntegrationTestTimeout,

0 commit comments

Comments
 (0)