diff --git a/Crashlytics/Crashlytics/FIRCrashlytics.m b/Crashlytics/Crashlytics/FIRCrashlytics.m index 4d112cddad43..3d5163e5aa9b 100644 --- a/Crashlytics/Crashlytics/FIRCrashlytics.m +++ b/Crashlytics/Crashlytics/FIRCrashlytics.m @@ -58,6 +58,7 @@ #import @import FirebaseSessions; +@import FirebaseRemoteConfigInterop; #if TARGET_OS_IPHONE #import @@ -104,7 +105,8 @@ - (instancetype)initWithApp:(FIRApp *)app appInfo:(NSDictionary *)appInfo installations:(FIRInstallations *)installations analytics:(id)analytics - sessions:(id)sessions { + sessions:(id)sessions + remoteConfig:(id)remoteConfig { self = [super init]; if (self) { @@ -206,6 +208,10 @@ + (void)load { FIRDependency *sessionsDep = [FIRDependency dependencyWithProtocol:@protocol(FIRSessionsProvider)]; + FIRDependency *remoteConfigDep = + [FIRDependency dependencyWithProtocol:@protocol(FIRRemoteConfigInterop)]; + + // break point on this also with rc creatioBlock and check the order FIRComponentCreationBlock creationBlock = ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) { if (!container.app.isDefaultApp) { @@ -215,6 +221,7 @@ + (void)load { id analytics = FIR_COMPONENT(FIRAnalyticsInterop, container); id sessions = FIR_COMPONENT(FIRSessionsProvider, container); + id remoteConfig = FIR_COMPONENT(FIRRemoteConfigInterop, container); FIRInstallations *installations = [FIRInstallations installationsWithApp:container.app]; @@ -224,13 +231,14 @@ + (void)load { appInfo:NSBundle.mainBundle.infoDictionary installations:installations analytics:analytics - sessions:sessions]; + sessions:sessions + remoteConfig:remoteConfig]; }; FIRComponent *component = [FIRComponent componentWithProtocol:@protocol(FIRCrashlyticsInstanceProvider) instantiationTiming:FIRInstantiationTimingEagerInDefaultApp - dependencies:@[ analyticsDep, sessionsDep ] + dependencies:@[ analyticsDep, sessionsDep, remoteConfigDep ] creationBlock:creationBlock]; return @[ component ]; } diff --git a/FirebaseRemoteConfig/Interop/RemoteConfigInterop.swift b/FirebaseRemoteConfig/Interop/RemoteConfigInterop.swift new file mode 100644 index 000000000000..9a1e2db6ca96 --- /dev/null +++ b/FirebaseRemoteConfig/Interop/RemoteConfigInterop.swift @@ -0,0 +1,14 @@ +// +// File.swift +// +// +// Created by Themis Wang on 2023-11-16. +// + +import Foundation + +@objc(FIRRemoteConfigInterop) +public protocol RemoteConfigInterop { + func registerRolloutsStateSubscriber(_ namespace: String, + subscriber: RolloutsStateSubscriber?) +} diff --git a/FirebaseRemoteConfig/Interop/RolloutAssignment.swift b/FirebaseRemoteConfig/Interop/RolloutAssignment.swift new file mode 100644 index 000000000000..563a3e0b301c --- /dev/null +++ b/FirebaseRemoteConfig/Interop/RolloutAssignment.swift @@ -0,0 +1,39 @@ +// +// File.swift +// +// +// Created by Themis Wang on 2023-11-16. +// + +import Foundation + +@objc(FIRRolloutAssignment) +public class RolloutAssignment: NSObject { + @objc public var rolloutId: String + @objc public var variantId: String + @objc public var templateVersion: String + @objc public var parameterKey: String + @objc public var parameterValue: String + + public init(rolloutId: String, variantId: String, templateVersion: String, parameterKey: String, + parameterValue: String) { + self.rolloutId = rolloutId + self.variantId = variantId + self.templateVersion = templateVersion + self.parameterKey = parameterKey + self.parameterValue = parameterValue + super.init() + } +} + +@objc(FIRRolloutsState) +public class RolloutsState: NSObject { + @objc public var assignments: Set = Set() + + public init(assignmentList: [RolloutAssignment]) { + for assignment in assignmentList { + assignments.insert(assignment) + } + super.init() + } +} diff --git a/FirebaseRemoteConfig/Interop/RolloutsStateSubscriber.swift b/FirebaseRemoteConfig/Interop/RolloutsStateSubscriber.swift new file mode 100644 index 000000000000..22c8df2430c1 --- /dev/null +++ b/FirebaseRemoteConfig/Interop/RolloutsStateSubscriber.swift @@ -0,0 +1,13 @@ +// +// File.swift +// +// +// Created by Themis Wang on 2023-11-16. +// + +import Foundation + +@objc(FIRRolloutsStateSubscriber) +public protocol RolloutsStateSubscriber { + func onRolloutsStateChanged(_ rolloutsState: RolloutsState) +} diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m index 4035d558707a..1fda435376e3 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m @@ -31,6 +31,8 @@ #import "FirebaseRemoteConfig/Sources/RCNDevice.h" #import "FirebaseRemoteConfig/Sources/RCNPersonalization.h" +@import FirebaseRemoteConfigInterop; + /// Remote Config Error Domain. /// TODO: Rename according to obj-c style for constants. NSString *const FIRRemoteConfigErrorDomain = @"com.google.remoteconfig.ErrorDomain"; @@ -73,6 +75,7 @@ @implementation FIRRemoteConfig { dispatch_queue_t _queue; NSString *_appName; NSMutableArray *_listeners; + NSMutableArray *_subscribers; } static NSMutableDictionary *> @@ -176,6 +179,8 @@ - (instancetype)initWithAppName:(NSString *)appName [_settings loadConfigFromMetadataTable]; + _subscribers = [[NSMutableArray alloc] init]; + if (analytics) { _listeners = [[NSMutableArray alloc] init]; RCNPersonalization *personalization = @@ -613,4 +618,8 @@ - (FIRConfigUpdateListenerRegistration *)addOnConfigUpdateListener: return [self->_configRealtime addConfigUpdateListener:listener]; } +- (void)addRemoteConfigInteropSubscriber:(id)subscriber { + [self->_subscribers addObject:subscriber]; +} + @end diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h b/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h index f015ea14974d..577c2ca6754f 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h @@ -17,6 +17,7 @@ #import #import "FirebaseCore/Extension/FirebaseCoreInternal.h" +@import FirebaseRemoteConfigInterop; @class FIRApp; @class FIRRemoteConfig; @@ -25,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN /// Provides and creates instances of Remote Config based on the namespace provided. Used in the /// interop registration process to keep track of RC instances for each `FIRApp` instance. -@protocol FIRRemoteConfigProvider +@protocol FIRRemoteConfigProvider /// Cached instances of Remote Config objects. @property(nonatomic, strong) NSMutableDictionary *instances; diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m b/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m index e0adc7bccbf5..a06d81103ce8 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m @@ -60,6 +60,7 @@ - (FIRRemoteConfig *)remoteConfigForNamespace:(NSString *)remoteConfigNamespace FIRApp *app = self.app; id analytics = app.isDefaultApp ? FIR_COMPONENT(FIRAnalyticsInterop, app.container) : nil; + instance = [[FIRRemoteConfig alloc] initWithAppName:app.name FIROptions:app.options namespace:remoteConfigNamespace @@ -95,6 +96,7 @@ + (void)load { + (NSArray *)componentsToRegister { FIRDependency *analyticsDep = [FIRDependency dependencyWithProtocol:@protocol(FIRAnalyticsInterop) isRequired:NO]; + FIRComponent *rcProvider = [FIRComponent componentWithProtocol:@protocol(FIRRemoteConfigProvider) instantiationTiming:FIRInstantiationTimingAlwaysEager @@ -107,4 +109,10 @@ + (void)load { return @[ rcProvider ]; } +- (void)registerRolloutsStateSubscriber:(nonnull NSString *)nameSpace + subscriber:(nullable id)subscriber { + // adding the registered subscriber reference to the namespace instance + [self.instances[nameSpace] addRemoteConfigInteropSubscriber:subscriber]; +} + @end diff --git a/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h b/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h index ef7def6fd9d3..c4935e82ce9e 100644 --- a/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h +++ b/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h @@ -22,6 +22,8 @@ @class RCNConfigDBManager; @class RCNConfigFetch; @class RCNConfigRealtime; + +@protocol FIRRemoteConfigComponentDelegate; @protocol FIRAnalyticsInterop; NS_ASSUME_NONNULL_BEGIN diff --git a/FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h b/FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h index 29cd12a5143a..f147f32b4313 100644 --- a/FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h +++ b/FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h @@ -17,6 +17,7 @@ #import @class FIRApp; +@protocol FIRRolloutsStateSubscriber; /// The Firebase Remote Config service default namespace, to be used if the API method does not /// specify a different namespace. Use the default namespace if configuring from the Google Firebase @@ -150,6 +151,8 @@ NS_SWIFT_NAME(RemoteConfigValue) @property(nonatomic, readonly, nullable) id JSONValue NS_SWIFT_NAME(jsonValue); /// Identifies the source of the fetched value. @property(nonatomic, readonly) FIRRemoteConfigSource source; + +@property(nonatomic, readonly, nonnull) NSMutableArray> *subscribers; @end #pragma mark - FIRRemoteConfigSettings @@ -357,4 +360,5 @@ typedef void (^FIRRemoteConfigUpdateCompletion)(FIRRemoteConfigUpdate *_Nullable (FIRRemoteConfigUpdateCompletion _Nonnull)listener NS_SWIFT_NAME(addOnConfigUpdateListener(remoteConfigUpdateCompletion:)); +- (void)addRemoteConfigInteropSubscriber:(nonnull id)subscriber; @end diff --git a/Package.swift b/Package.swift index 661d1a76295d..3cb72a8636b2 100644 --- a/Package.swift +++ b/Package.swift @@ -491,11 +491,16 @@ let package = Package( ), .target( name: "FirebaseCrashlytics", - dependencies: ["FirebaseCore", "FirebaseInstallations", "FirebaseSessions", - .product(name: "GoogleDataTransport", package: "GoogleDataTransport"), - .product(name: "GULEnvironment", package: "GoogleUtilities"), - .product(name: "FBLPromises", package: "Promises"), - .product(name: "nanopb", package: "nanopb")], + dependencies: [ + "FirebaseCore", + "FirebaseInstallations", + "FirebaseSessions", + "FirebaseRemoteConfigInterop", + .product(name: "GoogleDataTransport", package: "GoogleDataTransport"), + .product(name: "GULEnvironment", package: "GoogleUtilities"), + .product(name: "FBLPromises", package: "Promises"), + .product(name: "nanopb", package: "nanopb"), + ], path: "Crashlytics", exclude: [ "run", @@ -957,6 +962,7 @@ let package = Package( "FirebaseCore", "FirebaseABTesting", "FirebaseInstallations", + "FirebaseRemoteConfigInterop", .product(name: "GULNSData", package: "GoogleUtilities"), ], path: "FirebaseRemoteConfig/Sources", @@ -1028,6 +1034,15 @@ let package = Package( .headerSearchPath("../../../"), ] ), + // Internal headers only for consuming from other SDK. + .target( + name: "FirebaseRemoteConfigInterop", + path: "FirebaseRemoteConfig/Interop", + publicHeadersPath: ".", + cSettings: [ + .headerSearchPath("../../"), + ] + ), // MARK: - Firebase Sessions