Skip to content

Commit 6300732

Browse files
ddnanthemiswang
authored andcommittedFeb 21, 2024
[Rollouts] Notify RolloutsState change to Interop subscriber (#12334)
1 parent 8072c9d commit 6300732

17 files changed

+313
-113
lines changed
 

‎Crashlytics/Crashlytics/FIRCrashlytics.m

+12-14
Original file line numberDiff line numberDiff line change
@@ -171,19 +171,6 @@ - (instancetype)initWithApp:(FIRApp *)app
171171
[sessions registerWithSubscriber:self];
172172
}
173173

174-
if (remoteConfig) {
175-
FIRCLSDebugLog(@"Registering RemoteConfig SDK subscription for rollouts data");
176-
177-
FIRCLSRolloutsPersistenceManager *persistenceManager =
178-
[[FIRCLSRolloutsPersistenceManager alloc] initWithFileManager:_fileManager];
179-
_remoteConfigManager =
180-
[[FIRCLSRemoteConfigManager alloc] initWithRemoteConfig:remoteConfig
181-
persistenceDelegate:persistenceManager];
182-
183-
// TODO(themisw): Import "firebase" from the interop in the future.
184-
[remoteConfig registerRolloutsStateSubscriber:self for:@"firebase"];
185-
}
186-
187174
_reportUploader = [[FIRCLSReportUploader alloc] initWithManagerData:_managerData];
188175

189176
_existingReportManager =
@@ -216,8 +203,19 @@ - (instancetype)initWithApp:(FIRApp *)app
216203
}] catch:^void(NSError *error) {
217204
FIRCLSErrorLog(@"Crash reporting failed to initialize with error: %@", error);
218205
}];
219-
}
220206

207+
// RemoteConfig subscription should be made after session report directory created.
208+
if (remoteConfig) {
209+
FIRCLSDebugLog(@"Registering RemoteConfig SDK subscription for rollouts data");
210+
211+
FIRCLSRolloutsPersistenceManager *persistenceManager =
212+
[[FIRCLSRolloutsPersistenceManager alloc] initWithFileManager:_fileManager];
213+
_remoteConfigManager =
214+
[[FIRCLSRemoteConfigManager alloc] initWithRemoteConfig:remoteConfig
215+
persistenceDelegate:persistenceManager];
216+
[remoteConfig registerRolloutsStateSubscriber:self for:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform];
217+
}
218+
}
221219
return self;
222220
}
223221

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
@objc(FIRRemoteConfigConstants)
18+
public final class RemoteConfigConstants: NSObject {
19+
@objc(FIRNamespaceGoogleMobilePlatform) public static let NamespaceGoogleMobilePlatform =
20+
"firebase"
21+
}

‎FirebaseRemoteConfig/Sources/FIRRemoteConfig.m

+93-17
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
/// Notification when config is successfully activated
4646
const NSNotificationName FIRRemoteConfigActivateNotification =
4747
@"FIRRemoteConfigActivateNotification";
48+
static NSNotificationName FIRRolloutsStateDidChangeNotificationName =
49+
@"FIRRolloutsStateDidChangeNotification";
4850

4951
/// Listener for the get methods.
5052
typedef void (^FIRRemoteConfigListener)(NSString *_Nonnull, NSDictionary *_Nonnull);
@@ -79,8 +81,9 @@ @implementation FIRRemoteConfig {
7981
*RCInstances;
8082

8183
+ (nonnull FIRRemoteConfig *)remoteConfigWithApp:(FIRApp *_Nonnull)firebaseApp {
82-
return [FIRRemoteConfig remoteConfigWithFIRNamespace:FIRNamespaceGoogleMobilePlatform
83-
app:firebaseApp];
84+
return [FIRRemoteConfig
85+
remoteConfigWithFIRNamespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform
86+
app:firebaseApp];
8487
}
8588

8689
+ (nonnull FIRRemoteConfig *)remoteConfigWithFIRNamespace:(NSString *_Nonnull)firebaseNamespace {
@@ -116,8 +119,9 @@ + (FIRRemoteConfig *)remoteConfig {
116119
@"initializer in SwiftUI."];
117120
}
118121

119-
return [FIRRemoteConfig remoteConfigWithFIRNamespace:FIRNamespaceGoogleMobilePlatform
120-
app:[FIRApp defaultApp]];
122+
return [FIRRemoteConfig
123+
remoteConfigWithFIRNamespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform
124+
app:[FIRApp defaultApp]];
121125
}
122126

123127
/// Singleton instance of serial queue for queuing all incoming RC calls.
@@ -329,16 +333,20 @@ - (void)activateWithCompletion:(FIRRemoteConfigActivateChangeCompletion)completi
329333
// New config has been activated at this point
330334
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated.");
331335
[strongSelf->_configContent activatePersonalization];
332-
// Update activeRolloutMetadata
333-
[strongSelf->_configContent activateRolloutMetadata];
334336
// Update last active template version number in setting and userDefaults.
335-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
336-
[strongSelf->_settings updateLastActiveTemplateVersion];
337-
});
337+
[strongSelf->_settings updateLastActiveTemplateVersion];
338+
// Update activeRolloutMetadata
339+
[strongSelf->_configContent activateRolloutMetadata:^(BOOL success) {
340+
if (success) {
341+
[self notifyRolloutsStateChange:strongSelf->_configContent.activeRolloutMetadata
342+
versionNumber:strongSelf->_settings.lastActiveTemplateVersion];
343+
}
344+
}];
345+
338346
// Update experiments only for 3p namespace
339347
NSString *namespace = [strongSelf->_FIRNamespace
340348
substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location];
341-
if ([namespace isEqualToString:FIRNamespaceGoogleMobilePlatform]) {
349+
if ([namespace isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]) {
342350
dispatch_async(dispatch_get_main_queue(), ^{
343351
[self notifyConfigHasActivated];
344352
});
@@ -383,6 +391,17 @@ - (NSString *)fullyQualifiedNamespace:(NSString *)namespace {
383391
return fullyQualifiedNamespace;
384392
}
385393

394+
- (FIRRemoteConfigValue *)defaultValueForFullyQualifiedNamespace:(NSString *)namespace
395+
key:(NSString *)key {
396+
FIRRemoteConfigValue *value = self->_configContent.defaultConfig[namespace][key];
397+
if (!value) {
398+
value = [[FIRRemoteConfigValue alloc]
399+
initWithData:[NSData data]
400+
source:(FIRRemoteConfigSource)FIRRemoteConfigSourceStatic];
401+
}
402+
return value;
403+
}
404+
386405
#pragma mark - Get Config Result
387406

388407
- (FIRRemoteConfigValue *)objectForKeyedSubscript:(NSString *)key {
@@ -408,13 +427,7 @@ - (FIRRemoteConfigValue *)configValueForKey:(NSString *)key {
408427
config:[self->_configContent getConfigAndMetadataForNamespace:FQNamespace]];
409428
return;
410429
}
411-
value = self->_configContent.defaultConfig[FQNamespace][key];
412-
if (value) {
413-
return;
414-
}
415-
416-
value = [[FIRRemoteConfigValue alloc] initWithData:[NSData data]
417-
source:FIRRemoteConfigSourceStatic];
430+
value = [self defaultValueForFullyQualifiedNamespace:FQNamespace key:key];
418431
});
419432
return value;
420433
}
@@ -619,4 +632,67 @@ - (FIRConfigUpdateListenerRegistration *)addOnConfigUpdateListener:
619632
return [self->_configRealtime addConfigUpdateListener:listener];
620633
}
621634

635+
#pragma mark - Rollout
636+
637+
- (void)addRemoteConfigInteropSubscriber:(id<FIRRolloutsStateSubscriber>)subscriber {
638+
[[NSNotificationCenter defaultCenter]
639+
addObserverForName:FIRRolloutsStateDidChangeNotificationName
640+
object:self
641+
queue:nil
642+
usingBlock:^(NSNotification *_Nonnull notification) {
643+
FIRRolloutsState *rolloutsState =
644+
notification.userInfo[FIRRolloutsStateDidChangeNotificationName];
645+
[subscriber rolloutsStateDidChange:rolloutsState];
646+
}];
647+
// Send active rollout metadata stored in persistence while app launched if there is activeConfig
648+
NSString *fullyQualifiedNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
649+
NSDictionary<NSString *, NSDictionary *> *activeConfig = self->_configContent.activeConfig;
650+
if (activeConfig[fullyQualifiedNamespace] && activeConfig[fullyQualifiedNamespace].count > 0) {
651+
[self notifyRolloutsStateChange:self->_configContent.activeRolloutMetadata
652+
versionNumber:self->_settings.lastActiveTemplateVersion];
653+
}
654+
}
655+
656+
- (void)notifyRolloutsStateChange:(NSArray<NSDictionary *> *)rolloutMetadata
657+
versionNumber:(NSString *)versionNumber {
658+
NSArray<FIRRolloutAssignment *> *rolloutsAssignments =
659+
[self rolloutsAssignmentsWith:rolloutMetadata versionNumber:versionNumber];
660+
FIRRolloutsState *rolloutsState =
661+
[[FIRRolloutsState alloc] initWithAssignmentList:rolloutsAssignments];
662+
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069",
663+
@"Send rollouts state notification with name %@ to RemoteConfigInterop.",
664+
FIRRolloutsStateDidChangeNotificationName);
665+
[[NSNotificationCenter defaultCenter]
666+
postNotificationName:FIRRolloutsStateDidChangeNotificationName
667+
object:self
668+
userInfo:@{FIRRolloutsStateDidChangeNotificationName : rolloutsState}];
669+
}
670+
671+
- (NSArray<FIRRolloutAssignment *> *)rolloutsAssignmentsWith:
672+
(NSArray<NSDictionary *> *)rolloutMetadata
673+
versionNumber:(NSString *)versionNumber {
674+
NSMutableArray<FIRRolloutAssignment *> *rolloutsAssignments = [[NSMutableArray alloc] init];
675+
NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
676+
for (NSDictionary *metadata in rolloutMetadata) {
677+
NSString *rolloutId = metadata[RCNFetchResponseKeyRolloutID];
678+
NSString *variantID = metadata[RCNFetchResponseKeyVariantID];
679+
NSArray<NSString *> *affectedParameterKeys = metadata[RCNFetchResponseKeyAffectedParameterKeys];
680+
if (rolloutId && variantID && affectedParameterKeys) {
681+
for (NSString *key in affectedParameterKeys) {
682+
FIRRemoteConfigValue *value = self->_configContent.activeConfig[FQNamespace][key];
683+
if (!value) {
684+
value = [self defaultValueForFullyQualifiedNamespace:FQNamespace key:key];
685+
}
686+
FIRRolloutAssignment *assignment =
687+
[[FIRRolloutAssignment alloc] initWithRolloutId:rolloutId
688+
variantId:variantID
689+
templateVersion:[versionNumber longLongValue]
690+
parameterKey:key
691+
parameterValue:value.stringValue];
692+
[rolloutsAssignments addObject:assignment];
693+
}
694+
}
695+
}
696+
return rolloutsAssignments;
697+
}
622698
@end

‎FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m

+2-2
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ + (void)load {
148148

149149
- (void)registerRolloutsStateSubscriber:(id<FIRRolloutsStateSubscriber>)subscriber
150150
for:(NSString * _Nonnull)namespace {
151-
// TODO(Themisw): Adding the registered subscriber reference to the namespace instance
152-
// [self.instances[namespace] addRemoteConfigInteropSubscriber:subscriber];
151+
FIRRemoteConfig *instance = [self remoteConfigForNamespace:namespace];
152+
[instance addRemoteConfigInteropSubscriber:subscriber];
153153
}
154154

155155
@end

‎FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
@class RCNConfigFetch;
2424
@class RCNConfigRealtime;
2525
@protocol FIRAnalyticsInterop;
26+
@protocol FIRRolloutsStateSubscriber;
2627

2728
NS_ASSUME_NONNULL_BEGIN
2829

@@ -78,6 +79,9 @@ NS_ASSUME_NONNULL_BEGIN
7879
configContent:(RCNConfigContent *)configContent
7980
analytics:(nullable id<FIRAnalyticsInterop>)analytics;
8081

82+
/// Register RolloutsStateSubcriber to FIRRemoteConfig instance
83+
- (void)addRemoteConfigInteropSubscriber:(id<FIRRolloutsStateSubscriber> _Nonnull)subscriber;
84+
8185
@end
8286

8387
NS_ASSUME_NONNULL_END

‎FirebaseRemoteConfig/Sources/RCNConfigContent.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ typedef NS_ENUM(NSInteger, RCNDBSource) {
3939
@property(nonatomic, readonly, copy) NSDictionary *activeConfig;
4040
/// Local default config that is provided by external users;
4141
@property(nonatomic, readonly, copy) NSDictionary *defaultConfig;
42+
/// Active Rollout metadata that is currently used.
43+
@property(nonatomic, readonly, copy) NSArray<NSDictionary *> *activeRolloutMetadata;
4244

4345
- (instancetype)init NS_UNAVAILABLE;
4446

@@ -65,8 +67,8 @@ typedef NS_ENUM(NSInteger, RCNDBSource) {
6567
/// Gets the active config and Personalization metadata.
6668
- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace;
6769

68-
/// Sets the fetched rollout metadata to active and return the active rollout metadata.
69-
- (NSArray<NSDictionary *> *)activateRolloutMetadata;
70+
/// Sets the fetched rollout metadata to active with a success completion handler.
71+
- (void)activateRolloutMetadata:(void (^)(BOOL success))completionHandler;
7072

7173
/// Returns the updated parameters between fetched and active config.
7274
- (FIRRemoteConfigUpdate *)getConfigUpdateForNamespace:(NSString *)FIRNamespace;

‎FirebaseRemoteConfig/Sources/RCNConfigContent.m

+10-4
Original file line numberDiff line numberDiff line change
@@ -291,12 +291,13 @@ - (void)activatePersonalization {
291291
fromSource:RCNDBSourceActive];
292292
}
293293

294-
- (NSArray<NSDictionary *> *)activateRolloutMetadata {
294+
- (void)activateRolloutMetadata:(void (^)(BOOL success))completionHandler {
295295
_activeRolloutMetadata = _fetchedRolloutMetadata;
296296
[_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyActiveMetadata
297297
value:_activeRolloutMetadata
298-
completionHandler:nil];
299-
return _activeRolloutMetadata;
298+
completionHandler:^(BOOL success, NSDictionary *result) {
299+
completionHandler(success);
300+
}];
300301
}
301302

302303
#pragma mark State handling
@@ -364,7 +365,7 @@ - (void)handleUpdatePersonalization:(NSDictionary *)metadata {
364365

365366
- (void)handleUpdateRolloutFetchedMetadata:(NSArray<NSDictionary *> *)metadata {
366367
if (!metadata) {
367-
return;
368+
metadata = [[NSArray alloc] init];
368369
}
369370
_fetchedRolloutMetadata = metadata;
370371
[_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
@@ -399,6 +400,11 @@ - (NSDictionary *)activePersonalization {
399400
return _activePersonalization;
400401
}
401402

403+
- (NSArray<NSDictionary *> *)activeRolloutMetadata {
404+
[self checkAndWaitForInitialDatabaseLoad];
405+
return _activeRolloutMetadata;
406+
}
407+
402408
- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace {
403409
/// If this is the first time reading the active metadata, we might still be reading it from the
404410
/// database.

‎FirebaseRemoteConfig/Sources/RCNConfigFetch.m

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
2626
#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
2727
#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
28+
@import FirebaseRemoteConfigInterop;
2829

2930
#ifdef RCN_STAGING_SERVER
3031
static NSString *const kServerURLDomain =
@@ -572,7 +573,7 @@ - (void)fetchWithUserProperties:(NSDictionary *)userProperties
572573
// Update experiments only for 3p namespace
573574
NSString *namespace = [strongSelf->_FIRNamespace
574575
substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location];
575-
if ([namespace isEqualToString:FIRNamespaceGoogleMobilePlatform]) {
576+
if ([namespace isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]) {
576577
[strongSelf->_experiment updateExperimentsWithResponse:
577578
fetchedConfig[RCNFetchResponseKeyExperimentDescriptions]];
578579
}

‎FirebaseRemoteConfig/Sources/RCNConfigSettings.m

+1
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ - (void)updateMetadataWithFetchSuccessStatus:(BOOL)fetchSuccess
293293
[self updateLastFetchTimeInterval:[[NSDate date] timeIntervalSince1970]];
294294
// Note: We expect the googleAppID to always be available.
295295
_deviceContext = FIRRemoteConfigDeviceContextWithProjectIdentifier(_googleAppID);
296+
_lastFetchedTemplateVersion = templateVersion;
296297
[_userDefaultsManager setLastFetchedTemplateVersion:templateVersion];
297298
}
298299

‎FirebaseRemoteConfig/Sources/RCNConstants3P.m

-20
This file was deleted.

‎FirebaseRemoteConfig/Tests/Sample/RemoteConfigSampleApp/ViewController.m

+6-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#import <FirebaseRemoteConfig/FirebaseRemoteConfig.h>
2222
#import "../../../Sources/Private/FIRRemoteConfig_Private.h"
2323
#import "FRCLog.h"
24+
@import FirebaseRemoteConfigInterop;
2425

2526
static NSString *const FIRPerfNamespace = @"fireperf";
2627
static NSString *const FIRDefaultFIRAppName = @"__FIRAPP_DEFAULT";
@@ -81,7 +82,8 @@ - (void)viewDidLoad {
8182

8283
// TODO(mandard): Add support for deleting and adding namespaces in the app.
8384
self.namespacePickerData =
84-
[[NSArray alloc] initWithObjects:FIRNamespaceGoogleMobilePlatform, FIRPerfNamespace, nil];
85+
[[NSArray alloc] initWithObjects:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform,
86+
FIRPerfNamespace, nil];
8587
self.appPickerData =
8688
[[NSArray alloc] initWithObjects:FIRDefaultFIRAppName, FIRSecondFIRAppName, nil];
8789
self.RCInstances = [[NSMutableDictionary alloc] init];
@@ -91,7 +93,8 @@ - (void)viewDidLoad {
9193
if (!self.RCInstances[namespaceString]) {
9294
self.RCInstances[namespaceString] = [[NSMutableDictionary alloc] init];
9395
}
94-
if ([namespaceString isEqualToString:FIRNamespaceGoogleMobilePlatform] &&
96+
if ([namespaceString
97+
isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform] &&
9598
[appString isEqualToString:FIRDefaultFIRAppName]) {
9699
self.RCInstances[namespaceString][appString] = [FIRRemoteConfig remoteConfig];
97100
} else {
@@ -120,7 +123,7 @@ - (void)viewDidLoad {
120123
[alert addAction:defaultAction];
121124

122125
// Add realtime listener for firebase namespace
123-
[self.RCInstances[FIRNamespaceGoogleMobilePlatform][FIRDefaultFIRAppName]
126+
[self.RCInstances[FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform][FIRDefaultFIRAppName]
124127
addOnConfigUpdateListener:^(FIRRemoteConfigUpdate *_Nullable update,
125128
NSError *_Nullable error) {
126129
if (error != nil) {

0 commit comments

Comments
 (0)