-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Add support for other Firebase products to integrate with Remote Config. #6692
Changes from 4 commits
4a27403
550212d
b8fac84
fae0559
9a5c7f9
7bcef17
c4f83b2
d9996a4
a6574e4
2f396e3
9724b71
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 |
---|---|---|
|
@@ -28,6 +28,7 @@ | |
#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h" | ||
#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h" | ||
#import "FirebaseRemoteConfig/Sources/RCNDevice.h" | ||
#import "FirebaseRemoteConfig/Sources/RCNPersonalization.h" | ||
|
||
/// Remote Config Error Domain. | ||
/// TODO: Rename according to obj-c style for constants. | ||
|
@@ -41,6 +42,10 @@ | |
/// Timeout value for waiting on a fetch response. | ||
static NSString *const kRemoteConfigFetchTimeoutKey = @"_rcn_fetch_timeout"; | ||
|
||
/// Listener for the get methods. | ||
typedef void (^FIRRemoteConfigListener)(NSString *_Nonnull, NSDictionary *_Nonnull) | ||
NS_SWIFT_NAME(FIRRemoteConfigListener); | ||
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. Remove 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. Done. |
||
|
||
@interface FIRRemoteConfigSettings () { | ||
BOOL _developerModeEnabled; | ||
} | ||
|
@@ -68,6 +73,7 @@ @implementation FIRRemoteConfig { | |
RCNConfigExperiment *_configExperiment; | ||
dispatch_queue_t _queue; | ||
NSString *_appName; | ||
NSMutableArray *_listeners; | ||
} | ||
|
||
static NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, FIRRemoteConfig *> *> | ||
|
@@ -161,6 +167,14 @@ - (instancetype)initWithAppName:(NSString *)appName | |
options:options]; | ||
|
||
[_settings loadConfigFromMetadataTable]; | ||
|
||
if (analytics) { | ||
_listeners = [[NSMutableArray alloc] init]; | ||
[RCNPersonalization setAnalytics:analytics]; | ||
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. Could you please add tests for 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. Done. |
||
[self addListener:^(NSString *key, NSDictionary *config) { | ||
[RCNPersonalization logArmActive:key config:config]; | ||
}]; | ||
} | ||
} | ||
return self; | ||
} | ||
|
@@ -192,6 +206,24 @@ - (void)ensureInitializedWithCompletionHandler: | |
}); | ||
} | ||
|
||
/// Adds a listener that will be called whenever one of the get methods is called. | ||
/// @param listener Function that takes in the parameter key and the config. | ||
- (void)addListener:(nonnull FIRRemoteConfigListener)listener { | ||
@synchronized(_listeners) { | ||
[_listeners addObject:listener]; | ||
} | ||
} | ||
|
||
- (void)callListeners:(NSString *)key config:(NSDictionary *)config { | ||
@synchronized(_listeners) { | ||
for (FIRRemoteConfigListener listener in _listeners) { | ||
dispatch_async(_queue, ^{ | ||
listener(key, config); | ||
}); | ||
} | ||
} | ||
} | ||
|
||
#pragma mark - fetch | ||
|
||
- (void)fetchWithCompletionHandler:(FIRRemoteConfigFetchCompletion)completionHandler { | ||
|
@@ -286,6 +318,7 @@ - (void)activateWithCompletion:(FIRRemoteConfigActivateChangeCompletion)completi | |
forNamespace:self->_FIRNamespace]; | ||
strongSelf->_settings.lastApplyTimeInterval = [[NSDate date] timeIntervalSince1970]; | ||
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated."); | ||
[strongSelf->_configContent activatePersonalization]; | ||
[strongSelf->_configExperiment updateExperimentsWithHandler:^(NSError *_Nullable error) { | ||
if (completion) { | ||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | ||
|
@@ -328,6 +361,7 @@ - (FIRRemoteConfigValue *)configValueForKey:(NSString *)key { | |
@"Key %@ should come from source:%zd instead coming from source: %zd.", key, | ||
(long)FIRRemoteConfigSourceRemote, (long)value.source); | ||
} | ||
[self callListeners:key config:[self->_configContent getMetadataForNamespace:FQNamespace]]; | ||
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. nit: 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. Done. |
||
return; | ||
} | ||
value = self->_configContent.defaultConfig[FQNamespace][key]; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,11 @@ @implementation RCNConfigContent { | |
NSMutableDictionary *_fetchedConfig; | ||
/// Default config provided by user. | ||
NSMutableDictionary *_defaultConfig; | ||
/// Active Personalization metadata that is currently used. | ||
NSDictionary *_activePersonalization; | ||
/// Pending Personalization metadata that is latest data from server that might or might not be | ||
/// applied. | ||
NSDictionary *_fetchedPersonalization; | ||
/// DBManager | ||
RCNConfigDBManager *_DBManager; | ||
/// Current bundle identifier; | ||
|
@@ -72,14 +77,16 @@ - (instancetype)initWithDBManager:(RCNConfigDBManager *)DBManager { | |
_activeConfig = [[NSMutableDictionary alloc] init]; | ||
_fetchedConfig = [[NSMutableDictionary alloc] init]; | ||
_defaultConfig = [[NSMutableDictionary alloc] init]; | ||
_activePersonalization = [[NSDictionary alloc] init]; | ||
_fetchedPersonalization = [[NSDictionary alloc] init]; | ||
_bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; | ||
if (!_bundleIdentifier) { | ||
FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038", | ||
@"Main bundle identifier is missing. Remote Config might not work properly."); | ||
_bundleIdentifier = @""; | ||
} | ||
_DBManager = DBManager; | ||
_configLoadFromDBSemaphore = dispatch_semaphore_create(0); | ||
_configLoadFromDBSemaphore = dispatch_semaphore_create(1); | ||
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. Could you please add a comment why the particular semaphore value is set. Also, maybe it will be a bit more readable if we call 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. Moved the database methods closer. |
||
[self loadConfigFromMainTable]; | ||
} | ||
return self; | ||
|
@@ -204,10 +211,17 @@ - (void)updateConfigContentWithResponse:(NSDictionary *)response | |
if ([state isEqualToString:RCNFetchResponseKeyStateUpdate]) { | ||
[self handleUpdateStateForConfigNamespace:currentNamespace | ||
withEntries:response[RCNFetchResponseKeyEntries]]; | ||
[self handleUpdatePersonalization:response[RCNFetchResponseKeyPersonalizationMetadata]]; | ||
return; | ||
} | ||
} | ||
|
||
- (void)activatePersonalization { | ||
_activePersonalization = _fetchedPersonalization; | ||
[_DBManager insertOrUpdatePersonalizationConfig:_activePersonalization | ||
fromSource:RCNDBSourceActive]; | ||
} | ||
|
||
#pragma mark State handling | ||
- (void)handleNoChangeStateForConfigNamespace:(NSString *)currentNamespace { | ||
if (!_fetchedConfig[currentNamespace]) { | ||
|
@@ -263,6 +277,14 @@ - (void)handleUpdateStateForConfigNamespace:(NSString *)currentNamespace | |
} | ||
} | ||
|
||
- (void)handleUpdatePersonalization:(NSDictionary *)metadata { | ||
if (!metadata) { | ||
return; | ||
} | ||
_fetchedPersonalization = metadata; | ||
[_DBManager insertOrUpdatePersonalizationConfig:metadata fromSource:RCNDBSourceFetched]; | ||
} | ||
|
||
#pragma mark - database | ||
|
||
/// This method is only meant to be called at init time. The underlying logic will need to be | ||
|
@@ -284,6 +306,14 @@ - (void)loadConfigFromMainTable { | |
self->_defaultConfig = [defaultConfig mutableCopy]; | ||
dispatch_semaphore_signal(self->_configLoadFromDBSemaphore); | ||
}]; | ||
|
||
[_DBManager loadPersonalizationWithCompletionHandler:^( | ||
BOOL success, NSDictionary *fetchedPersonalization, | ||
NSDictionary *activePersonalization, NSDictionary *defaultConfig) { | ||
self->_fetchedPersonalization = [fetchedPersonalization copy]; | ||
self->_activePersonalization = [activePersonalization copy]; | ||
dispatch_semaphore_signal(self->_configLoadFromDBSemaphore); | ||
}]; | ||
} | ||
|
||
/// Update the current config result to main table. | ||
|
@@ -314,6 +344,16 @@ - (NSDictionary *)defaultConfig { | |
return _defaultConfig; | ||
} | ||
|
||
- (NSDictionary *)getMetadataForNamespace:(NSString *)FIRNamespace { | ||
/// If this is the first time reading the active metadata, we might still be reading it from the | ||
/// database. | ||
[self checkAndWaitForInitialDatabaseLoad]; | ||
return @{ | ||
RCNFetchResponseKeyEntries : _activeConfig[FIRNamespace], | ||
RCNFetchResponseKeyPersonalizationMetadata : _activePersonalization | ||
}; | ||
} | ||
|
||
/// We load the database async at init time. Block all further calls to active/fetched/default | ||
/// configs until load is done. | ||
/// @return Database load completion status. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,6 +73,9 @@ typedef void (^RCNDBLoadCompletion)(BOOL success, | |
/// Load experiment from experiment table. | ||
/// @param handler The callback when reading from DB is complete. | ||
- (void)loadExperimentWithCompletionHandler:(RCNDBCompletion)handler; | ||
/// Load Personalization from table. | ||
/// @param handler The callback when reading from DB is complete. | ||
- (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler; | ||
|
||
/// Insert a record in metadata table. | ||
/// @param columnNameToValue The column name and its value to be inserted in metadata table. | ||
|
@@ -100,6 +103,10 @@ typedef void (^RCNDBLoadCompletion)(BOOL success, | |
- (void)updateMetadataWithOption:(RCNUpdateOption)option | ||
values:(NSArray *)values | ||
completionHandler:(RCNDBCompletion)handler; | ||
|
||
/// Insert or update the data in Personalizatoin config. | ||
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. nit: typo Personalization 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. Done. |
||
- (BOOL)insertOrUpdatePersonalizationConfig:(NSDictionary *)metadata fromSource:(RCNDBSource)source; | ||
|
||
/// Clear the record of given namespace and package name | ||
/// before updating the table. | ||
- (void)deleteRecordFromMainTableWithNamespace:(NSString *)namespace_p | ||
|
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.
Can you make this more descriptive? As written this might raise concerns for developers. What exactly is the impact of the change for users of the RemoteConfig libraries?
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.
I think the RC team wants to keep this intentionally vague, since most developers won't be able to use this.
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.
In that case, something like "Added preliminary infrastructure to support future features" or nothing at all might be better. If I read the code right, additional logging is only happening when Personalization is being used. Saying unspecified logging is happening can be concerning to developers.
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.
cc: @morganchen12
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.
Changed to your suggestion.