Skip to content

Commit 95cf1d3

Browse files
authored
Add support for other Firebase products to integrate with Remote Config. (#6692)
1 parent 1bba5e7 commit 95cf1d3

File tree

11 files changed

+546
-27
lines changed

11 files changed

+546
-27
lines changed

FirebaseRemoteConfig.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ app update.
5454
# 'FirebaseRemoteConfig/Tests/Unit/RCNConfigTest.m',
5555
'FirebaseRemoteConfig/Tests/Unit/RCNConfigExperimentTest.m',
5656
'FirebaseRemoteConfig/Tests/Unit/RCNConfigValueTest.m',
57+
'FirebaseRemoteConfig/Tests/Unit/RCNPersonalizationTest.m',
5758
# 'FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfig+FIRAppTest.m',
5859
'FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m',
5960
# 'FirebaseRemoteConfig/Tests/Unit/RCNThrottlingTests.m',

FirebaseRemoteConfig/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# v7.1.0
2+
- [changed] Add support for other Firebase products to integrate with Remote Config. (#6692)
3+
14
# v7.0.0
25
- [changed] Updated `lastFetchTime` field to readonly. (#6567)
36
- [changed] Functionally neutral change to stop using a deprecated method in the AB Testing API. (#6543)

FirebaseRemoteConfig/Sources/FIRRemoteConfig.m

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
2929
#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
3030
#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
31+
#import "FirebaseRemoteConfig/Sources/RCNPersonalization.h"
3132

3233
/// Remote Config Error Domain.
3334
/// TODO: Rename according to obj-c style for constants.
@@ -39,6 +40,9 @@
3940
/// Timeout value for waiting on a fetch response.
4041
static NSString *const kRemoteConfigFetchTimeoutKey = @"_rcn_fetch_timeout";
4142

43+
/// Listener for the get methods.
44+
typedef void (^FIRRemoteConfigListener)(NSString *_Nonnull, NSDictionary *_Nonnull);
45+
4246
@implementation FIRRemoteConfigSettings
4347

4448
- (instancetype)init {
@@ -61,6 +65,7 @@ @implementation FIRRemoteConfig {
6165
RCNConfigExperiment *_configExperiment;
6266
dispatch_queue_t _queue;
6367
NSString *_appName;
68+
NSMutableArray *_listeners;
6469
}
6570

6671
static NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, FIRRemoteConfig *> *>
@@ -154,6 +159,15 @@ - (instancetype)initWithAppName:(NSString *)appName
154159
options:options];
155160

156161
[_settings loadConfigFromMetadataTable];
162+
163+
if (analytics) {
164+
_listeners = [[NSMutableArray alloc] init];
165+
RCNPersonalization *personalization =
166+
[[RCNPersonalization alloc] initWithAnalytics:analytics];
167+
[self addListener:^(NSString *key, NSDictionary *config) {
168+
[personalization logArmActive:key config:config];
169+
}];
170+
}
157171
}
158172
return self;
159173
}
@@ -185,6 +199,24 @@ - (void)ensureInitializedWithCompletionHandler:
185199
});
186200
}
187201

202+
/// Adds a listener that will be called whenever one of the get methods is called.
203+
/// @param listener Function that takes in the parameter key and the config.
204+
- (void)addListener:(nonnull FIRRemoteConfigListener)listener {
205+
@synchronized(_listeners) {
206+
[_listeners addObject:listener];
207+
}
208+
}
209+
210+
- (void)callListeners:(NSString *)key config:(NSDictionary *)config {
211+
@synchronized(_listeners) {
212+
for (FIRRemoteConfigListener listener in _listeners) {
213+
dispatch_async(_queue, ^{
214+
listener(key, config);
215+
});
216+
}
217+
}
218+
}
219+
188220
#pragma mark - fetch
189221

190222
- (void)fetchWithCompletionHandler:(FIRRemoteConfigFetchCompletion)completionHandler {
@@ -279,6 +311,7 @@ - (void)activateWithCompletion:(FIRRemoteConfigActivateChangeCompletion)completi
279311
forNamespace:self->_FIRNamespace];
280312
strongSelf->_settings.lastApplyTimeInterval = [[NSDate date] timeIntervalSince1970];
281313
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated.");
314+
[strongSelf->_configContent activatePersonalization];
282315
[strongSelf->_configExperiment updateExperimentsWithHandler:^(NSError *_Nullable error) {
283316
if (completion) {
284317
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@@ -321,6 +354,8 @@ - (FIRRemoteConfigValue *)configValueForKey:(NSString *)key {
321354
@"Key %@ should come from source:%zd instead coming from source: %zd.", key,
322355
(long)FIRRemoteConfigSourceRemote, (long)value.source);
323356
}
357+
[self callListeners:key
358+
config:[self->_configContent getConfigAndMetadataForNamespace:FQNamespace]];
324359
return;
325360
}
326361
value = self->_configContent.defaultConfig[FQNamespace][key];

FirebaseRemoteConfig/Sources/RCNConfigConstants.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ static const char *RCNRemoteConfigQueueLabel = "com.google.GoogleConfigService.F
3535
static NSString *const RCNFetchResponseKeyEntries = @"entries";
3636
/// Key that includes data for experiment descriptions in ABT.
3737
static NSString *const RCNFetchResponseKeyExperimentDescriptions = @"experimentDescriptions";
38+
/// Key that includes data for Personalization metadata.
39+
static NSString *const RCNFetchResponseKeyPersonalizationMetadata = @"personalizationMetadata";
3840
/// Error key.
3941
static NSString *const RCNFetchResponseKeyError = @"error";
4042
/// Error code.

FirebaseRemoteConfig/Sources/RCNConfigContent.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,10 @@ typedef NS_ENUM(NSInteger, RCNDBSource) {
5757
toSource:(RCNDBSource)source
5858
forNamespace:(NSString *)FIRNamespace;
5959

60+
/// Sets the fetched Personalization metadata to active.
61+
- (void)activatePersonalization;
62+
63+
/// Gets the active config and Personalization metadata.
64+
- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace;
65+
6066
@end

FirebaseRemoteConfig/Sources/RCNConfigContent.m

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ @implementation RCNConfigContent {
3232
NSMutableDictionary *_fetchedConfig;
3333
/// Default config provided by user.
3434
NSMutableDictionary *_defaultConfig;
35+
/// Active Personalization metadata that is currently used.
36+
NSDictionary *_activePersonalization;
37+
/// Pending Personalization metadata that is latest data from server that might or might not be
38+
/// applied.
39+
NSDictionary *_fetchedPersonalization;
3540
/// DBManager
3641
RCNConfigDBManager *_DBManager;
3742
/// Current bundle identifier;
@@ -72,14 +77,17 @@ - (instancetype)initWithDBManager:(RCNConfigDBManager *)DBManager {
7277
_activeConfig = [[NSMutableDictionary alloc] init];
7378
_fetchedConfig = [[NSMutableDictionary alloc] init];
7479
_defaultConfig = [[NSMutableDictionary alloc] init];
80+
_activePersonalization = [[NSDictionary alloc] init];
81+
_fetchedPersonalization = [[NSDictionary alloc] init];
7582
_bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
7683
if (!_bundleIdentifier) {
7784
FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038",
7885
@"Main bundle identifier is missing. Remote Config might not work properly.");
7986
_bundleIdentifier = @"";
8087
}
8188
_DBManager = DBManager;
82-
_configLoadFromDBSemaphore = dispatch_semaphore_create(0);
89+
// Waits for both config and Personalization data to load.
90+
_configLoadFromDBSemaphore = dispatch_semaphore_create(1);
8391
[self loadConfigFromMainTable];
8492
}
8593
return self;
@@ -93,6 +101,44 @@ - (BOOL)initializationSuccessful {
93101
return isDatabaseLoadSuccessful;
94102
}
95103

104+
#pragma mark - database
105+
106+
/// This method is only meant to be called at init time. The underlying logic will need to be
107+
/// revaluated if the assumption changes at a later time.
108+
- (void)loadConfigFromMainTable {
109+
if (!_DBManager) {
110+
return;
111+
}
112+
113+
NSAssert(!_isDatabaseLoadAlreadyInitiated, @"Database load has already been initiated");
114+
_isDatabaseLoadAlreadyInitiated = true;
115+
116+
[_DBManager
117+
loadMainWithBundleIdentifier:_bundleIdentifier
118+
completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
119+
NSDictionary *activeConfig, NSDictionary *defaultConfig) {
120+
self->_fetchedConfig = [fetchedConfig mutableCopy];
121+
self->_activeConfig = [activeConfig mutableCopy];
122+
self->_defaultConfig = [defaultConfig mutableCopy];
123+
dispatch_semaphore_signal(self->_configLoadFromDBSemaphore);
124+
}];
125+
126+
[_DBManager loadPersonalizationWithCompletionHandler:^(
127+
BOOL success, NSDictionary *fetchedPersonalization,
128+
NSDictionary *activePersonalization, NSDictionary *defaultConfig) {
129+
self->_fetchedPersonalization = [fetchedPersonalization copy];
130+
self->_activePersonalization = [activePersonalization copy];
131+
dispatch_semaphore_signal(self->_configLoadFromDBSemaphore);
132+
}];
133+
}
134+
135+
/// Update the current config result to main table.
136+
/// @param values Values in a row to write to the table.
137+
/// @param source The source the config data is coming from. It determines which table to write to.
138+
- (void)updateMainTableWithValues:(NSArray *)values fromSource:(RCNDBSource)source {
139+
[_DBManager insertMainTableWithValues:values fromSource:source completionHandler:nil];
140+
}
141+
96142
#pragma mark - update
97143
/// This function is for copying dictionary when user set up a default config or when user clicks
98144
/// activate. For now the DBSource can only be Active or Default.
@@ -204,10 +250,17 @@ - (void)updateConfigContentWithResponse:(NSDictionary *)response
204250
if ([state isEqualToString:RCNFetchResponseKeyStateUpdate]) {
205251
[self handleUpdateStateForConfigNamespace:currentNamespace
206252
withEntries:response[RCNFetchResponseKeyEntries]];
253+
[self handleUpdatePersonalization:response[RCNFetchResponseKeyPersonalizationMetadata]];
207254
return;
208255
}
209256
}
210257

258+
- (void)activatePersonalization {
259+
_activePersonalization = _fetchedPersonalization;
260+
[_DBManager insertOrUpdatePersonalizationConfig:_activePersonalization
261+
fromSource:RCNDBSourceActive];
262+
}
263+
211264
#pragma mark State handling
212265
- (void)handleNoChangeStateForConfigNamespace:(NSString *)currentNamespace {
213266
if (!_fetchedConfig[currentNamespace]) {
@@ -263,35 +316,14 @@ - (void)handleUpdateStateForConfigNamespace:(NSString *)currentNamespace
263316
}
264317
}
265318

266-
#pragma mark - database
267-
268-
/// This method is only meant to be called at init time. The underlying logic will need to be
269-
/// revaluated if the assumption changes at a later time.
270-
- (void)loadConfigFromMainTable {
271-
if (!_DBManager) {
319+
- (void)handleUpdatePersonalization:(NSDictionary *)metadata {
320+
if (!metadata) {
272321
return;
273322
}
274-
275-
NSAssert(!_isDatabaseLoadAlreadyInitiated, @"Database load has already been initiated");
276-
_isDatabaseLoadAlreadyInitiated = true;
277-
278-
[_DBManager
279-
loadMainWithBundleIdentifier:_bundleIdentifier
280-
completionHandler:^(BOOL success, NSDictionary *fetchedConfig,
281-
NSDictionary *activeConfig, NSDictionary *defaultConfig) {
282-
self->_fetchedConfig = [fetchedConfig mutableCopy];
283-
self->_activeConfig = [activeConfig mutableCopy];
284-
self->_defaultConfig = [defaultConfig mutableCopy];
285-
dispatch_semaphore_signal(self->_configLoadFromDBSemaphore);
286-
}];
323+
_fetchedPersonalization = metadata;
324+
[_DBManager insertOrUpdatePersonalizationConfig:metadata fromSource:RCNDBSourceFetched];
287325
}
288326

289-
/// Update the current config result to main table.
290-
/// @param values Values in a row to write to the table.
291-
/// @param source The source the config data is coming from. It determines which table to write to.
292-
- (void)updateMainTableWithValues:(NSArray *)values fromSource:(RCNDBSource)source {
293-
[_DBManager insertMainTableWithValues:values fromSource:source completionHandler:nil];
294-
}
295327
#pragma mark - getter/setter
296328
- (NSDictionary *)fetchedConfig {
297329
/// If this is the first time reading the fetchedConfig, we might still be reading it from the
@@ -314,6 +346,16 @@ - (NSDictionary *)defaultConfig {
314346
return _defaultConfig;
315347
}
316348

349+
- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace {
350+
/// If this is the first time reading the active metadata, we might still be reading it from the
351+
/// database.
352+
[self checkAndWaitForInitialDatabaseLoad];
353+
return @{
354+
RCNFetchResponseKeyEntries : _activeConfig[FIRNamespace],
355+
RCNFetchResponseKeyPersonalizationMetadata : _activePersonalization
356+
};
357+
}
358+
317359
/// We load the database async at init time. Block all further calls to active/fetched/default
318360
/// configs until load is done.
319361
/// @return Database load completion status.

FirebaseRemoteConfig/Sources/RCNConfigDBManager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ typedef void (^RCNDBLoadCompletion)(BOOL success,
7373
/// Load experiment from experiment table.
7474
/// @param handler The callback when reading from DB is complete.
7575
- (void)loadExperimentWithCompletionHandler:(RCNDBCompletion)handler;
76+
/// Load Personalization from table.
77+
/// @param handler The callback when reading from DB is complete.
78+
- (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler;
7679

7780
/// Insert a record in metadata table.
7881
/// @param columnNameToValue The column name and its value to be inserted in metadata table.
@@ -100,6 +103,10 @@ typedef void (^RCNDBLoadCompletion)(BOOL success,
100103
- (void)updateMetadataWithOption:(RCNUpdateOption)option
101104
values:(NSArray *)values
102105
completionHandler:(RCNDBCompletion)handler;
106+
107+
/// Insert or update the data in Personalization config.
108+
- (BOOL)insertOrUpdatePersonalizationConfig:(NSDictionary *)metadata fromSource:(RCNDBSource)source;
109+
103110
/// Clear the record of given namespace and package name
104111
/// before updating the table.
105112
- (void)deleteRecordFromMainTableWithNamespace:(NSString *)namespace_p

0 commit comments

Comments
 (0)