Skip to content

Commit 9af155f

Browse files
feat: add async versions of all public DevCycleClient methods with callbacks (#221)
* feat: add async versions of all public DevCycleClient methods with callbacks * feat: try and get tests working * feat: update tests * feat: cleanup test * feat: update anon user id test logic * feat: update swift example apps to use new methods Signed-off-by: Jonathan Norris <[email protected]> * feat: update fastlane gem --------- Signed-off-by: Jonathan Norris <[email protected]>
1 parent 6b6f293 commit 9af155f

File tree

25 files changed

+1219
-852
lines changed

25 files changed

+1219
-852
lines changed

DevCycle/DevCycleClient.swift

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,6 @@ public class DevCycleClient {
245245

246246
private func cacheUser(user: DevCycleUser) {
247247
self.cacheService.save(user: user)
248-
if user.isAnonymous == true, let userId = user.userId {
249-
self.cacheService.setAnonUserId(anonUserId: userId)
250-
}
251248
}
252249

253250
private func setupSSEConnection() {
@@ -442,6 +439,23 @@ public class DevCycleClient {
442439
})
443440
}
444441

442+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
443+
public func identifyUser(user: DevCycleUser) async throws -> [String: Variable]? {
444+
return try await withCheckedThrowingContinuation { continuation in
445+
do {
446+
try self.identifyUser(user: user) { error, variables in
447+
if let error = error {
448+
continuation.resume(throwing: error)
449+
} else {
450+
continuation.resume(returning: variables)
451+
}
452+
}
453+
} catch {
454+
continuation.resume(throwing: error)
455+
}
456+
}
457+
}
458+
445459
public func resetUser(callback: IdentifyCompletedHandler? = nil) throws {
446460
self.cache = cacheService.load()
447461
self.flushEvents()
@@ -475,6 +489,23 @@ public class DevCycleClient {
475489
})
476490
}
477491

492+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
493+
public func resetUser() async throws -> [String: Variable]? {
494+
return try await withCheckedThrowingContinuation { continuation in
495+
do {
496+
try self.resetUser { error, variables in
497+
if let error = error {
498+
continuation.resume(throwing: error)
499+
} else {
500+
continuation.resume(returning: variables)
501+
}
502+
}
503+
} catch {
504+
continuation.resume(throwing: error)
505+
}
506+
}
507+
}
508+
478509
public func allFeatures() -> [String: Feature] {
479510
return self.config?.userConfig?.features ?? [:]
480511
}
@@ -497,6 +528,20 @@ public class DevCycleClient {
497528
self.flushEvents(callback)
498529
}
499530

531+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
532+
public func flushEvents() async throws {
533+
try await withCheckedThrowingContinuation {
534+
(continuation: CheckedContinuation<Void, Error>) in
535+
self.flushEvents({ error in
536+
if let error = error {
537+
continuation.resume(throwing: error)
538+
} else {
539+
continuation.resume(returning: ())
540+
}
541+
})
542+
}
543+
}
544+
500545
internal func flushEvents(_ callback: FlushCompletedHandler? = nil) {
501546
if !self.eventQueue.isEmpty() {
502547
guard let user = self.user else {
@@ -529,6 +574,15 @@ public class DevCycleClient {
529574
self.sseConnection?.close()
530575
}
531576

577+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
578+
public func close() async {
579+
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
580+
self.close {
581+
continuation.resume(returning: ())
582+
}
583+
}
584+
}
585+
532586
public class ClientBuilder {
533587
private var client: DevCycleClient
534588
private var service: DevCycleServiceProtocol?
@@ -582,6 +636,25 @@ public class DevCycleClient {
582636
self.client = DevCycleClient()
583637
return result
584638
}
639+
640+
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
641+
public func build() async throws -> DevCycleClient {
642+
return try await withCheckedThrowingContinuation { continuation in
643+
do {
644+
var resultClient: DevCycleClient?
645+
let client = try self.build(onInitialized: { error in
646+
if let error = error {
647+
continuation.resume(throwing: error)
648+
} else if let resultClient = resultClient {
649+
continuation.resume(returning: resultClient)
650+
}
651+
})
652+
resultClient = client
653+
} catch {
654+
continuation.resume(throwing: error)
655+
}
656+
}
657+
}
585658
}
586659

587660
public static func builder() -> ClientBuilder {

DevCycle/DevCycleUser.swift

Lines changed: 41 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import Foundation
77

88
#if canImport(UIKit)
9-
import UIKit
9+
import UIKit
1010
#endif
1111

1212
enum UserError: Error {
@@ -19,90 +19,79 @@ enum UserError: Error {
1919

2020
public class UserBuilder {
2121
private let cacheService: CacheServiceProtocol = CacheService()
22-
22+
2323
var user: DevCycleUser
2424
var customData: [String: Any]?
2525
var privateCustomData: [String: Any]?
26-
26+
2727
init() {
2828
self.user = DevCycleUser()
2929
}
30-
30+
3131
public func userId(_ userId: String) -> UserBuilder {
3232
self.user.userId = userId
3333
self.user.isAnonymous = false
3434
return self
3535
}
36-
36+
3737
public func isAnonymous(_ isAnonymous: Bool) -> UserBuilder {
38-
if (self.user.isAnonymous != nil) { return self }
38+
if self.user.isAnonymous != nil { return self }
3939
self.user.isAnonymous = isAnonymous
40-
if (isAnonymous) {
41-
if let cachedAnonUserId = self.cacheService.getAnonUserId() {
42-
self.user.userId = cachedAnonUserId
43-
} else {
44-
let generatedAnonId = UUID().uuidString
45-
self.user.userId = generatedAnonId
46-
}
40+
if isAnonymous {
41+
self.user.userId = self.cacheService.getOrCreateAnonUserId()
4742
}
4843
return self
4944
}
50-
45+
5146
public func email(_ email: String) -> UserBuilder {
5247
self.user.email = email
5348
return self
5449
}
55-
50+
5651
public func name(_ name: String) -> UserBuilder {
5752
self.user.name = name
5853
return self
5954
}
60-
55+
6156
public func language(_ language: String) -> UserBuilder {
6257
self.user.language = language
6358
return self
6459
}
65-
60+
6661
public func country(_ country: String) -> UserBuilder {
6762
self.user.country = country
6863
return self
6964
}
70-
65+
7166
public func customData(_ customData: [String: Any]) -> UserBuilder {
7267
self.customData = customData
7368
return self
7469
}
75-
70+
7671
public func privateCustomData(_ privateCustomData: [String: Any]) -> UserBuilder {
7772
self.privateCustomData = privateCustomData
7873
return self
7974
}
80-
75+
8176
public func build() throws -> DevCycleUser {
8277
guard self.user.userId?.trimmingCharacters(in: .whitespacesAndNewlines) != ""
8378
else {
8479
throw UserError.InvalidUser
8580
}
86-
81+
8782
if self.user.userId == nil {
88-
if let cachedAnonUserId = self.cacheService.getAnonUserId() {
89-
self.user.userId = cachedAnonUserId
90-
} else {
91-
let generatedAnonId = UUID().uuidString
92-
self.user.userId = generatedAnonId
93-
}
83+
self.user.userId = self.cacheService.getOrCreateAnonUserId()
9484
self.user.isAnonymous = true
9585
}
9686

97-
9887
if let customData = self.customData {
9988
self.user.customData = try CustomData.customDataFromDic(customData)
10089
}
101-
90+
10291
if let privateCustomData = self.privateCustomData {
10392
self.user.privateCustomData = try CustomData.customDataFromDic(privateCustomData)
10493
}
105-
94+
10695
let result = self.user
10796
self.user = DevCycleUser()
10897
self.customData = nil
@@ -111,7 +100,6 @@ public class UserBuilder {
111100
}
112101
}
113102

114-
115103
public class DevCycleUser: Codable {
116104
public var userId: String?
117105
public var isAnonymous: Bool?
@@ -121,7 +109,7 @@ public class DevCycleUser: Codable {
121109
public var country: String?
122110
public var customData: CustomData?
123111
public var privateCustomData: CustomData?
124-
112+
125113
internal var lastSeenDate: Date
126114
internal let createdDate: Date
127115
internal let platform: String
@@ -131,7 +119,7 @@ public class DevCycleUser: Codable {
131119
internal let sdkVersion: String
132120
internal var appVersion: String?
133121
internal var appBuild: Int?
134-
122+
135123
init() {
136124
let platform = PlatformDetails()
137125
self.lastSeenDate = Date()
@@ -146,12 +134,14 @@ public class DevCycleUser: Codable {
146134
self.appBuild = Int(appBuildStr)
147135
}
148136
}
149-
137+
150138
enum CodingKeys: String, CodingKey {
151-
case userId = "user_id"
152-
case isAnonymous, email, name, language, country, appVersion, appBuild, customData, privateCustomData, lastSeenDate, createdDate, platform, platformVersion, deviceModel, sdkType, sdkVersion
139+
case userId = "user_id"
140+
case isAnonymous, email, name, language, country, appVersion, appBuild, customData,
141+
privateCustomData, lastSeenDate, createdDate, platform, platformVersion, deviceModel,
142+
sdkType, sdkVersion
153143
}
154-
144+
155145
public func update(with user: DevCycleUser) {
156146
self.lastSeenDate = Date()
157147
self.email = user.email
@@ -163,11 +153,11 @@ public class DevCycleUser: Codable {
163153
self.customData = user.customData
164154
self.privateCustomData = user.privateCustomData
165155
}
166-
156+
167157
public static func builder() -> UserBuilder {
168158
return UserBuilder()
169159
}
170-
160+
171161
func toQueryItems() -> [URLQueryItem] {
172162
let builder = QueryItemBuilder(user: self)
173163
.formatToQueryItem(name: "user_id", value: self.userId)
@@ -185,16 +175,18 @@ public class DevCycleUser: Codable {
185175
.formatToQueryItem(name: "deviceModel", value: self.deviceModel)
186176
.formatToQueryItem(name: "sdkType", value: self.sdkType)
187177
.formatToQueryItem(name: "sdkVersion", value: self.sdkVersion)
188-
178+
189179
if let customData = self.customData,
190-
let customDataJSON = try? JSONEncoder().encode(customData) {
180+
let customDataJSON = try? JSONEncoder().encode(customData)
181+
{
191182
_ = builder.formatToQueryItem(name: "customData", value: customDataJSON)
192183
}
193184
if let privateCustomData = self.privateCustomData,
194-
let privateCustomDataJSON = try? JSONEncoder().encode(privateCustomData) {
185+
let privateCustomDataJSON = try? JSONEncoder().encode(privateCustomData)
186+
{
195187
_ = builder.formatToQueryItem(name: "privateCustomData", value: privateCustomDataJSON)
196188
}
197-
189+
198190
return builder.build()
199191
}
200192
}
@@ -205,25 +197,26 @@ public typealias DVCUser = DevCycleUser
205197
class QueryItemBuilder {
206198
var items: [URLQueryItem]
207199
let user: DevCycleUser
208-
200+
209201
init(user: DevCycleUser) {
210202
self.items = []
211203
self.user = user
212204
}
213-
205+
214206
func formatToQueryItem<T>(name: String, value: T?) -> QueryItemBuilder {
215207
guard let property = value else { return self }
216208
if let map = property as? Data {
217-
items.append(URLQueryItem(name: name, value: String(data: map, encoding: String.Encoding.utf8)))
209+
items.append(
210+
URLQueryItem(name: name, value: String(data: map, encoding: String.Encoding.utf8)))
218211
} else if let date = property as? Date {
219212
items.append(URLQueryItem(name: name, value: "\(Int(date.timeIntervalSince1970))"))
220213
} else {
221214
items.append(URLQueryItem(name: name, value: "\(property)"))
222215
}
223-
216+
224217
return self
225218
}
226-
219+
227220
func build() -> [URLQueryItem] {
228221
let result = self.items
229222
self.items = []

0 commit comments

Comments
 (0)