-
Notifications
You must be signed in to change notification settings - Fork 72
Expand file tree
/
Copy pathRemoteConfigKey.swift
More file actions
118 lines (108 loc) · 3.18 KB
/
RemoteConfigKey.swift
File metadata and controls
118 lines (108 loc) · 3.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
@preconcurrency import Combine
import Dependencies
@preconcurrency import FirebaseRemoteConfig
import Sharing
extension SharedReaderKey {
static func remoteConfig<Value>(_ key: String) -> Self where Self == RemoteConfigKey<Value> {
RemoteConfigKey(key: key)
}
}
struct RemoteConfigKey<Value: Decodable & Sendable>: SharedReaderKey {
let key: String
let remoteConfig: any RemoteConfigClient
init(key: String) {
self.key = key
@Dependency(\.remoteConfig) var remoteConfig
self.remoteConfig = remoteConfig
}
var id: some Hashable { key }
func load(context _: LoadContext<Value>, continuation: LoadContinuation<Value>) {
remoteConfig.fetch(key: key, completion: continuation.resume(with:))
}
func subscribe(
context _: LoadContext<Value>, subscriber: SharedSubscriber<Value>
) -> SharedSubscription {
let cancellable = remoteConfig.addUpdateListener(
key: key,
subscriber: subscriber.yield(with:)
)
return SharedSubscription {
cancellable.cancel()
}
}
}
protocol RemoteConfigClient: Sendable {
func fetch<T: Decodable>(
key: String,
completion: @escaping (Result<T, any Error>) -> Void
)
func addUpdateListener<T: Decodable>(
key: String,
subscriber: @escaping (Result<T, any Error>) -> Void
) -> AnyCancellable
}
private enum RemoteConfigClientKey: DependencyKey {
static var liveValue: any RemoteConfigClient {
FirebaseRemoteConfig()
}
}
extension DependencyValues {
var remoteConfig: any RemoteConfigClient {
get { self[RemoteConfigClientKey.self] }
set { self[RemoteConfigClientKey.self] = newValue }
}
}
struct FirebaseRemoteConfig: RemoteConfigClient {
let remoteConfig = RemoteConfig.remoteConfig()
init() {
let settings = RemoteConfigSettings()
settings.minimumFetchInterval = 0
remoteConfig.configSettings = settings
}
func fetch<T: Decodable>(key: String, completion: @escaping (Result<T, any Error>) -> Void) {
remoteConfig.fetchAndActivate { _, error in
completion(
Result {
try remoteConfig.configValue(forKey: key).decoded()
}
)
}
}
func addUpdateListener<T: Decodable>(
key: String,
subscriber: @escaping (Result<T, any Error>) -> Void
) -> AnyCancellable {
let registration = remoteConfig.addOnConfigUpdateListener { _, error in
guard error == nil else { return }
remoteConfig.activate { changed, error in
guard error == nil else { return }
subscriber(Result { try remoteConfig.configValue(forKey: key).decoded() })
}
}
return AnyCancellable { registration.remove() }
}
}
final class MockRemoteConfig: RemoteConfigClient {
let config: [String: any Sendable]
init(config: [String: any Sendable]) {
self.config = config
}
func fetch<T: Decodable>(
key: String,
completion: @escaping (Result<T, any Error>) -> Void
) {
guard let value = config[key] as? T
else {
completion(.failure(NotFound()))
return
}
completion(.success(value))
}
func addUpdateListener<T: Decodable>(
key: String,
subscriber: @escaping (Result<T, any Error>) -> Void
) -> AnyCancellable {
AnyCancellable {}
}
struct NotFound: Error {}
}