Skip to content

Commit

Permalink
[Rollouts] Notify RolloutsState change to Interop subscriber (#12334)
Browse files Browse the repository at this point in the history
  • Loading branch information
ddnan authored and themiswang committed Feb 21, 2024
1 parent 8072c9d commit 6300732
Show file tree
Hide file tree
Showing 17 changed files with 313 additions and 113 deletions.
26 changes: 12 additions & 14 deletions Crashlytics/Crashlytics/FIRCrashlytics.m
Original file line number Diff line number Diff line change
Expand Up @@ -171,19 +171,6 @@ - (instancetype)initWithApp:(FIRApp *)app
[sessions registerWithSubscriber:self];
}

if (remoteConfig) {
FIRCLSDebugLog(@"Registering RemoteConfig SDK subscription for rollouts data");

FIRCLSRolloutsPersistenceManager *persistenceManager =
[[FIRCLSRolloutsPersistenceManager alloc] initWithFileManager:_fileManager];
_remoteConfigManager =
[[FIRCLSRemoteConfigManager alloc] initWithRemoteConfig:remoteConfig
persistenceDelegate:persistenceManager];

// TODO(themisw): Import "firebase" from the interop in the future.
[remoteConfig registerRolloutsStateSubscriber:self for:@"firebase"];
}

_reportUploader = [[FIRCLSReportUploader alloc] initWithManagerData:_managerData];

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

// RemoteConfig subscription should be made after session report directory created.
if (remoteConfig) {
FIRCLSDebugLog(@"Registering RemoteConfig SDK subscription for rollouts data");

FIRCLSRolloutsPersistenceManager *persistenceManager =
[[FIRCLSRolloutsPersistenceManager alloc] initWithFileManager:_fileManager];
_remoteConfigManager =
[[FIRCLSRemoteConfigManager alloc] initWithRemoteConfig:remoteConfig
persistenceDelegate:persistenceManager];
[remoteConfig registerRolloutsStateSubscriber:self for:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform];
}
}
return self;
}

Expand Down
21 changes: 21 additions & 0 deletions FirebaseRemoteConfig/Interop/RemoteConfigConstants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

@objc(FIRRemoteConfigConstants)
public final class RemoteConfigConstants: NSObject {
@objc(FIRNamespaceGoogleMobilePlatform) public static let NamespaceGoogleMobilePlatform =
"firebase"
}
110 changes: 93 additions & 17 deletions FirebaseRemoteConfig/Sources/FIRRemoteConfig.m
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
/// Notification when config is successfully activated
const NSNotificationName FIRRemoteConfigActivateNotification =
@"FIRRemoteConfigActivateNotification";
static NSNotificationName FIRRolloutsStateDidChangeNotificationName =
@"FIRRolloutsStateDidChangeNotification";

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

+ (nonnull FIRRemoteConfig *)remoteConfigWithApp:(FIRApp *_Nonnull)firebaseApp {
return [FIRRemoteConfig remoteConfigWithFIRNamespace:FIRNamespaceGoogleMobilePlatform
app:firebaseApp];
return [FIRRemoteConfig
remoteConfigWithFIRNamespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform
app:firebaseApp];
}

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

return [FIRRemoteConfig remoteConfigWithFIRNamespace:FIRNamespaceGoogleMobilePlatform
app:[FIRApp defaultApp]];
return [FIRRemoteConfig
remoteConfigWithFIRNamespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform
app:[FIRApp defaultApp]];
}

/// Singleton instance of serial queue for queuing all incoming RC calls.
Expand Down Expand Up @@ -329,16 +333,20 @@ - (void)activateWithCompletion:(FIRRemoteConfigActivateChangeCompletion)completi
// New config has been activated at this point
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated.");
[strongSelf->_configContent activatePersonalization];
// Update activeRolloutMetadata
[strongSelf->_configContent activateRolloutMetadata];
// Update last active template version number in setting and userDefaults.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[strongSelf->_settings updateLastActiveTemplateVersion];
});
[strongSelf->_settings updateLastActiveTemplateVersion];
// Update activeRolloutMetadata
[strongSelf->_configContent activateRolloutMetadata:^(BOOL success) {
if (success) {
[self notifyRolloutsStateChange:strongSelf->_configContent.activeRolloutMetadata
versionNumber:strongSelf->_settings.lastActiveTemplateVersion];
}
}];

// Update experiments only for 3p namespace
NSString *namespace = [strongSelf->_FIRNamespace
substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location];
if ([namespace isEqualToString:FIRNamespaceGoogleMobilePlatform]) {
if ([namespace isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self notifyConfigHasActivated];
});
Expand Down Expand Up @@ -383,6 +391,17 @@ - (NSString *)fullyQualifiedNamespace:(NSString *)namespace {
return fullyQualifiedNamespace;
}

- (FIRRemoteConfigValue *)defaultValueForFullyQualifiedNamespace:(NSString *)namespace
key:(NSString *)key {
FIRRemoteConfigValue *value = self->_configContent.defaultConfig[namespace][key];
if (!value) {
value = [[FIRRemoteConfigValue alloc]
initWithData:[NSData data]
source:(FIRRemoteConfigSource)FIRRemoteConfigSourceStatic];
}
return value;
}

#pragma mark - Get Config Result

- (FIRRemoteConfigValue *)objectForKeyedSubscript:(NSString *)key {
Expand All @@ -408,13 +427,7 @@ - (FIRRemoteConfigValue *)configValueForKey:(NSString *)key {
config:[self->_configContent getConfigAndMetadataForNamespace:FQNamespace]];
return;
}
value = self->_configContent.defaultConfig[FQNamespace][key];
if (value) {
return;
}

value = [[FIRRemoteConfigValue alloc] initWithData:[NSData data]
source:FIRRemoteConfigSourceStatic];
value = [self defaultValueForFullyQualifiedNamespace:FQNamespace key:key];
});
return value;
}
Expand Down Expand Up @@ -619,4 +632,67 @@ - (FIRConfigUpdateListenerRegistration *)addOnConfigUpdateListener:
return [self->_configRealtime addConfigUpdateListener:listener];
}

#pragma mark - Rollout

- (void)addRemoteConfigInteropSubscriber:(id<FIRRolloutsStateSubscriber>)subscriber {
[[NSNotificationCenter defaultCenter]
addObserverForName:FIRRolloutsStateDidChangeNotificationName
object:self
queue:nil
usingBlock:^(NSNotification *_Nonnull notification) {
FIRRolloutsState *rolloutsState =
notification.userInfo[FIRRolloutsStateDidChangeNotificationName];
[subscriber rolloutsStateDidChange:rolloutsState];
}];
// Send active rollout metadata stored in persistence while app launched if there is activeConfig
NSString *fullyQualifiedNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
NSDictionary<NSString *, NSDictionary *> *activeConfig = self->_configContent.activeConfig;
if (activeConfig[fullyQualifiedNamespace] && activeConfig[fullyQualifiedNamespace].count > 0) {
[self notifyRolloutsStateChange:self->_configContent.activeRolloutMetadata
versionNumber:self->_settings.lastActiveTemplateVersion];
}
}

- (void)notifyRolloutsStateChange:(NSArray<NSDictionary *> *)rolloutMetadata
versionNumber:(NSString *)versionNumber {
NSArray<FIRRolloutAssignment *> *rolloutsAssignments =
[self rolloutsAssignmentsWith:rolloutMetadata versionNumber:versionNumber];
FIRRolloutsState *rolloutsState =
[[FIRRolloutsState alloc] initWithAssignmentList:rolloutsAssignments];
FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069",
@"Send rollouts state notification with name %@ to RemoteConfigInterop.",
FIRRolloutsStateDidChangeNotificationName);
[[NSNotificationCenter defaultCenter]
postNotificationName:FIRRolloutsStateDidChangeNotificationName
object:self
userInfo:@{FIRRolloutsStateDidChangeNotificationName : rolloutsState}];
}

- (NSArray<FIRRolloutAssignment *> *)rolloutsAssignmentsWith:
(NSArray<NSDictionary *> *)rolloutMetadata
versionNumber:(NSString *)versionNumber {
NSMutableArray<FIRRolloutAssignment *> *rolloutsAssignments = [[NSMutableArray alloc] init];
NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
for (NSDictionary *metadata in rolloutMetadata) {
NSString *rolloutId = metadata[RCNFetchResponseKeyRolloutID];
NSString *variantID = metadata[RCNFetchResponseKeyVariantID];
NSArray<NSString *> *affectedParameterKeys = metadata[RCNFetchResponseKeyAffectedParameterKeys];
if (rolloutId && variantID && affectedParameterKeys) {
for (NSString *key in affectedParameterKeys) {
FIRRemoteConfigValue *value = self->_configContent.activeConfig[FQNamespace][key];
if (!value) {
value = [self defaultValueForFullyQualifiedNamespace:FQNamespace key:key];
}
FIRRolloutAssignment *assignment =
[[FIRRolloutAssignment alloc] initWithRolloutId:rolloutId
variantId:variantID
templateVersion:[versionNumber longLongValue]
parameterKey:key
parameterValue:value.stringValue];
[rolloutsAssignments addObject:assignment];
}
}
}
return rolloutsAssignments;
}
@end
4 changes: 2 additions & 2 deletions FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ + (void)load {

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

@end
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
@class RCNConfigFetch;
@class RCNConfigRealtime;
@protocol FIRAnalyticsInterop;
@protocol FIRRolloutsStateSubscriber;

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -78,6 +79,9 @@ NS_ASSUME_NONNULL_BEGIN
configContent:(RCNConfigContent *)configContent
analytics:(nullable id<FIRAnalyticsInterop>)analytics;

/// Register RolloutsStateSubcriber to FIRRemoteConfig instance
- (void)addRemoteConfigInteropSubscriber:(id<FIRRolloutsStateSubscriber> _Nonnull)subscriber;

@end

NS_ASSUME_NONNULL_END
6 changes: 4 additions & 2 deletions FirebaseRemoteConfig/Sources/RCNConfigContent.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ typedef NS_ENUM(NSInteger, RCNDBSource) {
@property(nonatomic, readonly, copy) NSDictionary *activeConfig;
/// Local default config that is provided by external users;
@property(nonatomic, readonly, copy) NSDictionary *defaultConfig;
/// Active Rollout metadata that is currently used.
@property(nonatomic, readonly, copy) NSArray<NSDictionary *> *activeRolloutMetadata;

- (instancetype)init NS_UNAVAILABLE;

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

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

/// Returns the updated parameters between fetched and active config.
- (FIRRemoteConfigUpdate *)getConfigUpdateForNamespace:(NSString *)FIRNamespace;
Expand Down
14 changes: 10 additions & 4 deletions FirebaseRemoteConfig/Sources/RCNConfigContent.m
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,13 @@ - (void)activatePersonalization {
fromSource:RCNDBSourceActive];
}

- (NSArray<NSDictionary *> *)activateRolloutMetadata {
- (void)activateRolloutMetadata:(void (^)(BOOL success))completionHandler {
_activeRolloutMetadata = _fetchedRolloutMetadata;
[_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyActiveMetadata
value:_activeRolloutMetadata
completionHandler:nil];
return _activeRolloutMetadata;
completionHandler:^(BOOL success, NSDictionary *result) {
completionHandler(success);
}];
}

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

- (void)handleUpdateRolloutFetchedMetadata:(NSArray<NSDictionary *> *)metadata {
if (!metadata) {
return;
metadata = [[NSArray alloc] init];
}
_fetchedRolloutMetadata = metadata;
[_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
Expand Down Expand Up @@ -399,6 +400,11 @@ - (NSDictionary *)activePersonalization {
return _activePersonalization;
}

- (NSArray<NSDictionary *> *)activeRolloutMetadata {
[self checkAndWaitForInitialDatabaseLoad];
return _activeRolloutMetadata;
}

- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace {
/// If this is the first time reading the active metadata, we might still be reading it from the
/// database.
Expand Down
3 changes: 2 additions & 1 deletion FirebaseRemoteConfig/Sources/RCNConfigFetch.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
@import FirebaseRemoteConfigInterop;

#ifdef RCN_STAGING_SERVER
static NSString *const kServerURLDomain =
Expand Down Expand Up @@ -572,7 +573,7 @@ - (void)fetchWithUserProperties:(NSDictionary *)userProperties
// Update experiments only for 3p namespace
NSString *namespace = [strongSelf->_FIRNamespace
substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location];
if ([namespace isEqualToString:FIRNamespaceGoogleMobilePlatform]) {
if ([namespace isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]) {
[strongSelf->_experiment updateExperimentsWithResponse:
fetchedConfig[RCNFetchResponseKeyExperimentDescriptions]];
}
Expand Down
1 change: 1 addition & 0 deletions FirebaseRemoteConfig/Sources/RCNConfigSettings.m
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ - (void)updateMetadataWithFetchSuccessStatus:(BOOL)fetchSuccess
[self updateLastFetchTimeInterval:[[NSDate date] timeIntervalSince1970]];
// Note: We expect the googleAppID to always be available.
_deviceContext = FIRRemoteConfigDeviceContextWithProjectIdentifier(_googleAppID);
_lastFetchedTemplateVersion = templateVersion;
[_userDefaultsManager setLastFetchedTemplateVersion:templateVersion];
}

Expand Down
20 changes: 0 additions & 20 deletions FirebaseRemoteConfig/Sources/RCNConstants3P.m

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#import <FirebaseRemoteConfig/FirebaseRemoteConfig.h>
#import "../../../Sources/Private/FIRRemoteConfig_Private.h"
#import "FRCLog.h"
@import FirebaseRemoteConfigInterop;

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

// TODO(mandard): Add support for deleting and adding namespaces in the app.
self.namespacePickerData =
[[NSArray alloc] initWithObjects:FIRNamespaceGoogleMobilePlatform, FIRPerfNamespace, nil];
[[NSArray alloc] initWithObjects:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform,
FIRPerfNamespace, nil];
self.appPickerData =
[[NSArray alloc] initWithObjects:FIRDefaultFIRAppName, FIRSecondFIRAppName, nil];
self.RCInstances = [[NSMutableDictionary alloc] init];
Expand All @@ -91,7 +93,8 @@ - (void)viewDidLoad {
if (!self.RCInstances[namespaceString]) {
self.RCInstances[namespaceString] = [[NSMutableDictionary alloc] init];
}
if ([namespaceString isEqualToString:FIRNamespaceGoogleMobilePlatform] &&
if ([namespaceString
isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform] &&
[appString isEqualToString:FIRDefaultFIRAppName]) {
self.RCInstances[namespaceString][appString] = [FIRRemoteConfig remoteConfig];
} else {
Expand Down Expand Up @@ -120,7 +123,7 @@ - (void)viewDidLoad {
[alert addAction:defaultAction];

// Add realtime listener for firebase namespace
[self.RCInstances[FIRNamespaceGoogleMobilePlatform][FIRDefaultFIRAppName]
[self.RCInstances[FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform][FIRDefaultFIRAppName]
addOnConfigUpdateListener:^(FIRRemoteConfigUpdate *_Nullable update,
NSError *_Nullable error) {
if (error != nil) {
Expand Down
Loading

0 comments on commit 6300732

Please sign in to comment.