Skip to content

Commit a8e3617

Browse files
Complete implementation for setting/updating custom signals with tests
1 parent 4c2bc89 commit a8e3617

File tree

8 files changed

+155
-2
lines changed

8 files changed

+155
-2
lines changed

FirebaseRemoteConfig/Sources/FIRRemoteConfig.m

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
/// Remote Config Error Domain.
3535
/// TODO: Rename according to obj-c style for constants.
3636
NSString *const FIRRemoteConfigErrorDomain = @"com.google.remoteconfig.ErrorDomain";
37+
// Remote Config Custom Signals Error Domain
38+
NSString *const FIRRemoteConfigCustomSignalsErrorDomain = @"com.google.remoteconfig.customsignals.ErrorDomain";
3739
// Remote Config Realtime Error Domain
3840
NSString *const FIRRemoteConfigUpdateErrorDomain = @"com.google.remoteconfig.update.ErrorDomain";
3941
/// Remote Config Error Info End Time Seconds;
@@ -239,7 +241,52 @@ - (void)callListeners:(NSString *)key config:(NSDictionary *)config {
239241

240242
- (void)setCustomSignals:(nullable NSDictionary<NSString *, NSObject *> *)customSignals
241243
WithCompletion:(void (^_Nullable)(NSError *_Nullable error))completionHandler {
242-
// TODO: Implement.
244+
void (^setCustomSignalsBlock)(void) = ^{
245+
if (!customSignals) {
246+
if (completionHandler) {
247+
dispatch_async(dispatch_get_main_queue(), ^{
248+
completionHandler(nil);
249+
});
250+
}
251+
return;
252+
}
253+
254+
for (NSString *key in customSignals) {
255+
NSObject *value = customSignals[key];
256+
if (value && ![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSNumber class]]) {
257+
if (completionHandler) {
258+
dispatch_async(dispatch_get_main_queue(), ^{
259+
NSError *error = [NSError errorWithDomain:FIRRemoteConfigCustomSignalsErrorDomain
260+
code:FIRRemoteConfigCustomSignalsErrorInvalidValueType
261+
userInfo:@{NSLocalizedDescriptionKey: @"Invalid value type. Must be NSString or NSNumber."}];
262+
completionHandler(error);
263+
});
264+
}
265+
return;
266+
}
267+
}
268+
269+
NSMutableDictionary<NSString *, NSObject *> *newCustomSignals =
270+
[[NSMutableDictionary alloc] initWithDictionary:self->_settings.customSignals];
271+
272+
for (NSString *key in customSignals) {
273+
NSObject *value = customSignals[key];
274+
if (value) {
275+
[newCustomSignals setObject:value forKey:key];
276+
} else {
277+
[newCustomSignals removeObjectForKey:key];
278+
}
279+
}
280+
281+
self->_settings.customSignals = newCustomSignals;
282+
if (completionHandler) {
283+
dispatch_async(dispatch_get_main_queue(), ^{
284+
completionHandler(nil);
285+
});
286+
}
287+
};
288+
289+
dispatch_async(_queue, setCustomSignalsBlock);
243290
}
244291

245292
#pragma mark - fetch

FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@
8484
/// Last active template version.
8585
@property(nonatomic, readwrite, assign) NSString *lastActiveTemplateVersion;
8686

87+
#pragma mark - Custom Signals
88+
89+
/// A dictionary to hold custom signals that are set by the developer.
90+
@property (nonatomic, readwrite, strong) NSMutableDictionary<NSString *, NSObject *> *customSignals;
91+
8792
#pragma mark Throttling properties
8893

8994
/// Throttling intervals are based on https://cloud.google.com/storage/docs/exponential-backoff

FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ typedef NS_ERROR_ENUM(FIRRemoteConfigUpdateErrorDomain, FIRRemoteConfigUpdateErr
9696
FIRRemoteConfigUpdateErrorUnavailable = 8004,
9797
} NS_SWIFT_NAME(RemoteConfigUpdateError);
9898

99+
/// Error domain for custom signals errors.
100+
extern NSString *const _Nonnull FIRRemoteConfigCustomSignalsErrorDomain NS_SWIFT_NAME(RemoteConfigCustomSignalsErrorDomain);
101+
102+
/// Firebase Remote Config custom signals error.
103+
typedef NS_ERROR_ENUM(FIRRemoteConfigCustomSignalsErrorDomain, FIRRemoteConfigCustomSignalsError) {
104+
/// Unknown error.
105+
FIRRemoteConfigCustomSignalsErrorUnknown = 8001,
106+
/// Invalid value type in the custom signals dictionary.
107+
FIRRemoteConfigCustomSignalsErrorInvalidValueType = 8002,
108+
} NS_SWIFT_NAME(RemoteConfigCustomSignalsError);
109+
99110
/// Enumerated value that indicates the source of Remote Config data. Data can come from
100111
/// the Remote Config service, the DefaultConfig that is available when the app is first installed,
101112
/// or a static initialized value if data is not available from the service or DefaultConfig.

FirebaseRemoteConfig/Sources/RCNConfigSettings.m

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ - (instancetype)initWithDatabaseManager:(RCNConfigDBManager *)manager
108108
@"New config database created. Resetting user defaults.");
109109
[_userDefaultsManager resetUserDefaults];
110110
}
111-
111+
112+
_customSignals = [_userDefaultsManager customSignals];
112113
_isFetchInProgress = NO;
113114
_lastFetchedTemplateVersion = [_userDefaultsManager lastFetchedTemplateVersion];
114115
_lastActiveTemplateVersion = [_userDefaultsManager lastActiveTemplateVersion];
@@ -444,6 +445,21 @@ - (NSString *)nextRequestWithUserProperties:(NSDictionary *)userProperties {
444445
}
445446
}
446447
}
448+
449+
if (_customSignals.count > 0) {
450+
NSError *error;
451+
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:_customSignals
452+
options:0
453+
error:&error];
454+
if (!error) {
455+
ret = [ret
456+
stringByAppendingString:[NSString
457+
stringWithFormat:@", custom_signals:%@",
458+
[[NSString alloc]
459+
initWithData:jsonData
460+
encoding:NSUTF8StringEncoding]]];
461+
}
462+
}
447463
ret = [ret stringByAppendingString:@"}"];
448464
return ret;
449465
}
@@ -517,6 +533,11 @@ - (void)setLastSetDefaultsTimeInterval:(NSTimeInterval)lastSetDefaultsTimestamp
517533
completionHandler:nil];
518534
}
519535

536+
- (void)setCustomSignals:(NSMutableDictionary<NSString *,NSObject *> *)customSignals {
537+
_customSignals = customSignals;
538+
[_userDefaultsManager setCustomSignals:customSignals];
539+
}
540+
520541
#pragma mark Throttling
521542

522543
- (BOOL)hasMinimumFetchIntervalElapsed:(NSTimeInterval)minimumFetchInterval {

FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ NS_ASSUME_NONNULL_BEGIN
4747
@property(nonatomic, assign) NSString *lastFetchedTemplateVersion;
4848
/// Last active template version.
4949
@property(nonatomic, assign) NSString *lastActiveTemplateVersion;
50+
/// A dictionary to hold the latest custom signals set by the developer.
51+
@property (nonatomic, readwrite, strong) NSMutableDictionary<NSString *, NSObject *> *customSignals;
5052

5153
/// Designated initializer.
5254
- (instancetype)initWithAppName:(NSString *)appName

FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
static NSString *const kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval =
3535
@"currentRealtimeThrottlingRetryInterval";
3636
static NSString *const kRCNUserDefaultsKeyNameRealtimeRetryCount = @"realtimeRetryCount";
37+
static NSString *const kRCNUserDefaultsKeyCustomSignals = @"customSignals";
3738

3839
@interface RCNUserDefaultsManager () {
3940
/// User Defaults instance for this bundleID. NSUserDefaults is guaranteed to be thread-safe.
@@ -141,6 +142,21 @@ - (void)setLastActiveTemplateVersion:(NSString *)templateVersion {
141142
}
142143
}
143144

145+
- (NSMutableDictionary<NSString *, NSObject *> *)customSignals {
146+
NSDictionary *userDefaults = [self instanceUserDefaults];
147+
if ([userDefaults objectForKey:kRCNUserDefaultsKeyCustomSignals]) {
148+
return [userDefaults objectForKey:kRCNUserDefaultsKeyCustomSignals];
149+
}
150+
151+
return [[NSMutableDictionary<NSString *, NSObject *> alloc] init];
152+
}
153+
154+
- (void)setCustomSignals:(NSMutableDictionary<NSString *, NSObject *> *)customSignals {
155+
if (customSignals) {
156+
[self setInstanceUserDefaultsValue:customSignals forKey:kRCNUserDefaultsKeyCustomSignals];
157+
}
158+
}
159+
144160
- (NSTimeInterval)lastETagUpdateTime {
145161
NSNumber *lastETagUpdateTime =
146162
[[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNamelastETagUpdateTime];

FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,30 @@ - (void)testFetchAndActivateRolloutsNotifyInterop {
18341834
[self waitForExpectations:@[ notificationExpectation ] timeout:_expectationTimeout];
18351835
}
18361836

1837+
- (void)testSetCustomSingals {
1838+
NSMutableArray<XCTestExpectation *> *expectations =
1839+
[[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
1840+
1841+
for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
1842+
expectations[i] = [self expectationWithDescription:
1843+
[NSString stringWithFormat:@"Set custom signals - instance %d", i]];
1844+
1845+
NSDictionary<NSString *, NSObject *> *testSignals = @{
1846+
@"signal1" : @"stringValue",
1847+
@"signal2" : @"stringValue2",
1848+
};
1849+
1850+
[_configInstances[i] setCustomSignals:testSignals
1851+
WithCompletion:^(NSError *_Nullable error) {
1852+
XCTAssertNil(error);
1853+
NSMutableDictionary<NSString *, NSObject *> *retrievedSignals = self->_configInstances[i].settings.customSignals;
1854+
XCTAssertEqualObjects(retrievedSignals, testSignals);
1855+
[expectations[i] fulfill];
1856+
}];
1857+
}
1858+
[self waitForExpectationsWithTimeout:_expectationTimeout handler:nil];
1859+
}
1860+
18371861
#pragma mark - Test Helpers
18381862

18391863
- (FIROptions *)firstAppOptions {

FirebaseRemoteConfig/Tests/Unit/RCNUserDefaultsManagerTests.m

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
static NSString* const AppName = @"testApp";
2424
static NSString* const FQNamespace1 = @"testNamespace1:testApp";
2525
static NSString* const FQNamespace2 = @"testNamespace2:testApp";
26+
static NSMutableDictionary<NSString *, NSObject *> *customSignals1 = nil;
27+
static NSMutableDictionary<NSString *, NSObject *> *customSignals2 = nil;
2628

2729
@interface RCNUserDefaultsManagerTests : XCTestCase
2830

@@ -36,6 +38,13 @@ - (void)setUp {
3638
[[NSUserDefaults standardUserDefaults]
3739
removePersistentDomainForName:[NSBundle mainBundle].bundleIdentifier];
3840
RCNUserDefaultsSampleTimeStamp = [[NSDate date] timeIntervalSince1970];
41+
42+
customSignals1 = [[NSMutableDictionary alloc] initWithDictionary:@{
43+
@"signal1" : @"stringValue",
44+
}];
45+
customSignals2 = [[NSMutableDictionary alloc] initWithDictionary:@{
46+
@"signal2" : @"stringValue2",
47+
}];
3948
}
4049

4150
- (void)testUserDefaultsEtagWriteAndRead {
@@ -168,6 +177,18 @@ - (void)testUserDefaultsCurrentRealtimeThrottlingRetryIntervalWriteAndRead {
168177
RCNUserDefaultsSampleTimeStamp - 2.0);
169178
}
170179

180+
- (void)testUserDefaultsCustomSignalsWriteAndRead {
181+
RCNUserDefaultsManager* manager =
182+
[[RCNUserDefaultsManager alloc] initWithAppName:AppName
183+
bundleID:[NSBundle mainBundle].bundleIdentifier
184+
namespace:FQNamespace1];
185+
[manager setCustomSignals:customSignals1];
186+
XCTAssertEqualObjects([manager customSignals], customSignals1);
187+
188+
[manager setCustomSignals:customSignals2];
189+
XCTAssertEqualObjects([manager customSignals], customSignals2);
190+
}
191+
171192
- (void)testUserDefaultsForMultipleNamespaces {
172193
RCNUserDefaultsManager* manager1 =
173194
[[RCNUserDefaultsManager alloc] initWithAppName:AppName
@@ -248,6 +269,12 @@ - (void)testUserDefaultsForMultipleNamespaces {
248269
[manager2 setLastActiveTemplateVersion:@"2"];
249270
XCTAssertEqualObjects([manager1 lastActiveTemplateVersion], @"1");
250271
XCTAssertEqualObjects([manager2 lastActiveTemplateVersion], @"2");
272+
273+
/// Custom Singnals
274+
[manager1 setCustomSignals:customSignals1];
275+
[manager2 setCustomSignals:customSignals2];
276+
XCTAssertEqualObjects([manager1 customSignals], customSignals1);
277+
XCTAssertEqualObjects([manager2 customSignals], customSignals2);
251278
}
252279

253280
- (void)testUserDefaultsReset {

0 commit comments

Comments
 (0)