Skip to content

Commit 97349c3

Browse files
committed
[#639] Resolve comments
1 parent 327c4a7 commit 97349c3

6 files changed

Lines changed: 91 additions & 71 deletions

File tree

template/Modules/Data/Sources/AppConfig/AppConfig.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,16 @@ public protocol AppConfigProtocol<DecodedConfig> {
2626
public final class AppConfig<DecodedConfig: Sendable>: AppConfigProtocol {
2727

2828
private var remoteConfig: RemoteConfig?
29-
private let defaultConfig: AppDefaultConfig
29+
private var configUpdateListenerRegistration: ConfigUpdateListenerRegistration?
30+
private var didSetUp = false
31+
private let remoteConfigDefaults: AppDefaultConfig
3032
private let configMapper: (RemoteConfigDecoder) -> DecodedConfig
3133
private let currentConfigSubject: CurrentValueSubject<DecodedConfig, Never>
3234

35+
deinit {
36+
configUpdateListenerRegistration?.remove()
37+
}
38+
3339
public var currentConfigPublisher: AnyPublisher<DecodedConfig, Never> {
3440
currentConfigSubject.eraseToAnyPublisher()
3541
}
@@ -39,16 +45,18 @@ public final class AppConfig<DecodedConfig: Sendable>: AppConfigProtocol {
3945
}
4046

4147
public init(
42-
defaultConfig: AppDefaultConfig = AppDefaultConfig(),
43-
initialConfig: DecodedConfig,
48+
remoteConfigDefaults: AppDefaultConfig = AppDefaultConfig(),
49+
bootstrapDecodedConfig: DecodedConfig,
4450
configMapper: @escaping (RemoteConfigDecoder) -> DecodedConfig
4551
) {
46-
self.defaultConfig = defaultConfig
52+
self.remoteConfigDefaults = remoteConfigDefaults
4753
self.configMapper = configMapper
48-
currentConfigSubject = CurrentValueSubject(initialConfig)
54+
currentConfigSubject = CurrentValueSubject(bootstrapDecodedConfig)
4955
}
5056

5157
public func setUp() {
58+
guard !didSetUp else { return }
59+
didSetUp = true
5260
remoteConfig = RemoteConfig.remoteConfig()
5361
setUpConfigSettings()
5462
setUpDefaults()
@@ -57,11 +65,14 @@ public final class AppConfig<DecodedConfig: Sendable>: AppConfigProtocol {
5765
}
5866

5967
public func getAllKeysFromDefault() -> [String] {
60-
remoteConfig?.allKeys(from: .default) ?? []
68+
if let remoteConfig {
69+
return remoteConfig.allKeys(from: .default).sorted()
70+
}
71+
return remoteConfigDefaults.configs.keys.map(\.stringValue).sorted()
6172
}
6273

6374
public func getAllKeysFromRemote() -> [String] {
64-
remoteConfig?.allKeys(from: .remote) ?? []
75+
remoteConfig?.allKeys(from: .remote).sorted() ?? []
6576
}
6677
}
6778

@@ -79,7 +90,7 @@ extension AppConfig {
7990

8091
private func setUpDefaults() {
8192
do {
82-
try remoteConfig?.setDefaults(from: defaultConfig)
93+
try remoteConfig?.setDefaults(from: remoteConfigDefaults)
8394
} catch {
8495
#if DEBUG || DEV
8596
NSLog("[AppConfig] Failed to set defaults: \(error).")
@@ -88,12 +99,12 @@ extension AppConfig {
8899
}
89100

90101
private func setUpListener() {
91-
remoteConfig?.addOnConfigUpdateListener { [weak self] _, error in
102+
configUpdateListenerRegistration = remoteConfig?.addOnConfigUpdateListener { [weak self] _, error in
92103
guard let self else { return }
93104
if logError(error, context: "Listener update") { return }
94105
remoteConfig?.activate { [weak self] _, error in
95106
guard let self else { return }
96-
if logError(error, context: "Listener activate") { return }
107+
_ = logError(error, context: "Listener activate")
97108
publishUpdatedConfig()
98109
}
99110
}
@@ -102,7 +113,7 @@ extension AppConfig {
102113
private func fetchAndActivate() {
103114
remoteConfig?.fetchAndActivate { [weak self] _, error in
104115
guard let self else { return }
105-
if logError(error, context: "Fetch and activate") { return }
116+
_ = logError(error, context: "Fetch and activate")
106117
publishUpdatedConfig()
107118
}
108119
}

template/Modules/Data/Sources/AppConfig/ExampleAppConfiguration.swift

Lines changed: 49 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,58 +4,59 @@
44

55
import Foundation
66

7-
/// Example configuration structure that demonstrates how to use AppConfig.
8-
/// Replace this with your app-specific configuration in real projects.
9-
public struct ExampleAppConfiguration: Sendable {
10-
11-
public let isFeatureEnabled: Bool
12-
public let maxRetryCount: Int
13-
public let apiTimeout: Double
14-
public let welcomeMessage: String
15-
16-
public init(
17-
isFeatureEnabled: Bool = false,
18-
maxRetryCount: Int = 3,
19-
apiTimeout: Double = 30.0,
20-
welcomeMessage: String = "Welcome"
21-
) {
22-
self.isFeatureEnabled = isFeatureEnabled
23-
self.maxRetryCount = maxRetryCount
24-
self.apiTimeout = apiTimeout
25-
self.welcomeMessage = welcomeMessage
26-
}
27-
}
28-
297
public enum ExampleConfigKey: String, CodingKey {
308

31-
case isFeatureEnabled = "feature_enabled"
32-
case maxRetryCount = "max_retry_count"
33-
case apiTimeout = "api_timeout"
34-
case welcomeMessage = "welcome_message"
9+
case isFeatureEnabled = "feature_enabled"
10+
case maxRetryCount = "max_retry_count"
11+
case apiTimeout = "api_timeout"
12+
case welcomeMessage = "welcome_message"
3513
}
3614

37-
public func createExampleAppConfig() -> AppConfig<ExampleAppConfiguration> {
38-
let defaultConfig = AppDefaultConfig.build(configs: [
39-
ExampleConfigKey.isFeatureEnabled.rawValue: false,
40-
ExampleConfigKey.maxRetryCount.rawValue: 3,
41-
ExampleConfigKey.apiTimeout.rawValue: 30.0,
42-
ExampleConfigKey.welcomeMessage.rawValue: "Welcome to {PROJECT_NAME}"
15+
public struct ExampleAppConfiguration: RemoteConfigDecodable {
16+
17+
public let isFeatureEnabled: Bool
18+
public let maxRetryCount: Int
19+
public let apiTimeout: Double
20+
public let welcomeMessage: String
21+
22+
public init(
23+
isFeatureEnabled: Bool = false,
24+
maxRetryCount: Int = 3,
25+
apiTimeout: Double = 30.0,
26+
welcomeMessage: String = "Welcome to {PROJECT_NAME}"
27+
) {
28+
self.isFeatureEnabled = isFeatureEnabled
29+
self.maxRetryCount = maxRetryCount
30+
self.apiTimeout = apiTimeout
31+
self.welcomeMessage = welcomeMessage
32+
}
33+
34+
public init(decoder: RemoteConfigDecoder) {
35+
let bootstrap = ExampleAppConfiguration()
36+
self.init(
37+
isFeatureEnabled: decoder.decodeBool(forKey: ExampleConfigKey.isFeatureEnabled.rawValue)
38+
?? bootstrap.isFeatureEnabled,
39+
maxRetryCount: decoder.decodeNumber(forKey: ExampleConfigKey.maxRetryCount.rawValue)?.intValue
40+
?? bootstrap.maxRetryCount,
41+
apiTimeout: decoder.decodeNumber(forKey: ExampleConfigKey.apiTimeout.rawValue)?.doubleValue
42+
?? bootstrap.apiTimeout,
43+
welcomeMessage: decoder.decodeString(forKey: ExampleConfigKey.welcomeMessage.rawValue)
44+
?? bootstrap.welcomeMessage
45+
)
46+
}
47+
48+
public static func makeAppConfig() -> AppConfig<ExampleAppConfiguration> {
49+
let bootstrap = ExampleAppConfiguration()
50+
let remoteDefaults = AppDefaultConfig.build(configs: [
51+
ExampleConfigKey.isFeatureEnabled.rawValue: bootstrap.isFeatureEnabled,
52+
ExampleConfigKey.maxRetryCount.rawValue: bootstrap.maxRetryCount,
53+
ExampleConfigKey.apiTimeout.rawValue: bootstrap.apiTimeout,
54+
ExampleConfigKey.welcomeMessage.rawValue: bootstrap.welcomeMessage,
4355
])
44-
45-
let initialConfig = ExampleAppConfiguration()
46-
47-
let configMapper: (RemoteConfigDecoder) -> ExampleAppConfiguration = { decoder in
48-
ExampleAppConfiguration(
49-
isFeatureEnabled: decoder.decodeBool(forKey: ExampleConfigKey.isFeatureEnabled.rawValue) ?? false,
50-
maxRetryCount: decoder.decodeNumber(forKey: ExampleConfigKey.maxRetryCount.rawValue)?.intValue ?? 3,
51-
apiTimeout: decoder.decodeNumber(forKey: ExampleConfigKey.apiTimeout.rawValue)?.doubleValue ?? 30.0,
52-
welcomeMessage: decoder.decodeString(forKey: ExampleConfigKey.welcomeMessage.rawValue) ?? "Welcome"
53-
)
54-
}
55-
5656
return AppConfig(
57-
defaultConfig: defaultConfig,
58-
initialConfig: initialConfig,
59-
configMapper: configMapper
57+
remoteConfigDefaults: remoteDefaults,
58+
bootstrapDecodedConfig: bootstrap,
59+
configMapper: { $0.init(decoder: $0) }
6060
)
61-
}
61+
}
62+
}

template/Modules/Data/Sources/AppConfig/FirebaseRemoteConfigSource.swift

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import Foundation
88

99
// MARK: - RemoteConfigInterface
1010

11-
/// Abstracts the Firebase `RemoteConfig` API surface used by `FirebaseRemoteConfigSource`,
12-
/// enabling the class to be tested without subclassing the Firebase singleton.
11+
/// Abstracts the Firebase `RemoteConfig` API surface used by `FirebaseRemoteConfigSource`.
1312
protocol RemoteConfigInterface: Sendable {
1413
func fetchAndActivate(completionHandler: ((RemoteConfigFetchAndActivateStatus, (any Error)?) -> Void)?)
1514
func configEntry(forKey key: String) -> (data: Data, source: FirebaseRemoteConfig.RemoteConfigSource)
@@ -62,18 +61,17 @@ public final class FirebaseRemoteConfigSource: RemoteConfigSource {
6261
}
6362

6463
if let string = String(data: data, encoding: .utf8) {
65-
if let boolValue = string.normalizedRemoteConfigBoolean {
66-
return .bool(boolValue)
67-
}
68-
69-
if let intValue = Int(string.trimmingCharacters(in: .whitespacesAndNewlines)) {
64+
let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)
65+
66+
if let intValue = Int(trimmed) {
7067
return .int(intValue)
7168
}
72-
73-
if let doubleValue = Double(string.trimmingCharacters(in: .whitespacesAndNewlines)) {
69+
if let doubleValue = Double(trimmed) {
7470
return .double(doubleValue)
7571
}
76-
72+
if let boolValue = trimmed.normalizedRemoteConfigBoolean {
73+
return .bool(boolValue)
74+
}
7775
return .string(string)
7876
}
7977

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// RemoteConfigDecodable.swift
3+
//
4+
5+
import FirebaseRemoteConfig
6+
7+
public protocol RemoteConfigDecodable: Sendable {
8+
9+
init(decoder: RemoteConfigDecoder)
10+
}

template/Modules/Data/Sources/AppConfig/RemoteConfigDecoder.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public struct RemoteConfigDecoder {
7878

7979
private func log(_ message: String) {
8080
#if DEBUG || DEV
81-
NSLog("[AppConfig] \(message).")
81+
NSLog("[RemoteConfigDecoder] \(message).")
8282
#endif
8383
}
8484
}

template/Modules/Data/Sources/Extensions/Container+Data.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@ extension Container {
5353

5454
/// Example AppConfig factory. Replace with your app-specific configuration.
5555
public var exampleAppConfig: Factory<AppConfig<ExampleAppConfiguration>> {
56-
self { createExampleAppConfig() }.singleton
56+
self { ExampleAppConfiguration.makeAppConfig() }.singleton
5757
}
5858
}

0 commit comments

Comments
 (0)