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