Skip to content
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

[Rollouts] Notify RolloutsState change to Interop subscriber #12334

Merged
merged 19 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
ddnan marked this conversation as resolved.
Show resolved Hide resolved
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
themiswang marked this conversation as resolved.
Show resolved Hide resolved
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 {
ddnan marked this conversation as resolved.
Show resolved Hide resolved
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) {
ddnan marked this conversation as resolved.
Show resolved Hide resolved
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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, i like how you factored this out 👍

}
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
Loading