Skip to content

Commit 9696203

Browse files
committed
Minor tweaks
1 parent 95179f7 commit 9696203

8 files changed

+143
-17
lines changed

README.md

+67
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,70 @@ UpgradeAlert.showAlert(appInfo: .init(version: "1.0.1", trackViewUrl: "https://a
104104
- Testing and Coverage Improving tests to cover edge cases and error scenarios would enhance the reliability of the application. For instance:
105105
- UpgradeAlertTests.swift: The test cases could be expanded to cover more scenarios, including error handling and user interaction outcomes.
106106
- Upgrade this to Swift 6.0 (Might be a bit tricky)
107+
- Add a way to inject text for alert. so we can localize text from the caller etc.as we might want to use .modules with localizations etc
108+
- Enhance Error Handling with Swift's Result Type
109+
Issue: The current implementation of asynchronous methods uses custom closures with optional parameters for error handling. This can be improved by leveraging Swift's Result type, which provides a clearer and more structured way to handle success and failure cases.
110+
Improvement: Refactor asynchronous methods to use Result instead of optional parameters. This will make the code more readable and maintainable.
111+
112+
```
113+
public final class UpgradeAlert {
114+
public static func checkForUpdates(completion: @escaping (Result<Void, UAError>) -> Void) {
115+
DispatchQueue.global(qos: .background).async {
116+
getAppInfo { result in
117+
switch result {
118+
case .success(let appInfo):
119+
let needsUpdate = ComparisonResult.compareVersion(
120+
current: Bundle.version ?? "0",
121+
appStore: appInfo.version
122+
) == .requiresUpgrade
123+
guard needsUpdate else {
124+
completion(.success(()))
125+
return
126+
}
127+
DispatchQueue.main.async {
128+
showAlert(appInfo: appInfo, completion: completion)
129+
}
130+
case .failure(let error):
131+
completion(.failure(error))
132+
}
133+
}
134+
}
135+
}
136+
}
137+
```
138+
139+
- Use Result in getAppInfo Method
140+
Issue: The getAppInfo method currently uses a closure with optional parameters for error handling.
141+
Improvement: Modify getAppInfo to use Result<AppInfo, UAError> in its completion handler.
142+
Updated Code:
143+
144+
```swift
145+
private static func getAppInfo(completion: @escaping (Result<AppInfo, UAError>) -> Void) {
146+
guard let url = requestURL else {
147+
completion(.failure(.invalidURL))
148+
return
149+
}
150+
let task = URLSession.shared.dataTask(with: url) { data, _, error in
151+
if let error = error {
152+
completion(.failure(.invalidResponse(description: error.localizedDescription)))
153+
return
154+
}
155+
guard let data = data else {
156+
completion(.failure(.invalidResponse(description: "No data received")))
157+
return
158+
}
159+
do {
160+
let result = try JSONDecoder().decode(LookupResult.self, from: data)
161+
if let appInfo = result.results.first {
162+
completion(.success(appInfo))
163+
} else {
164+
completion(.failure(.invalidResponse(description: "No app info available")))
165+
}
166+
} catch {
167+
completion(.failure(.invalidResponse(description: error.localizedDescription)))
168+
}
169+
}
170+
task.resume()
171+
}
172+
```
173+

Sources/UpgradeAlert/UpgradeAlert+Variables.swift

+8
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ extension UpgradeAlert {
1414
* - Fixme: ⚠️️ Consider renaming this to appInfoRequestURL for clarity.
1515
* - fix: maybe move url to const?
1616
*/
17+
@available(*, deprecated, renamed: "getRequestURL")
1718
internal static var requestURL: URL? {
1819
guard let bundleId: String = Bundle.identifier else { return nil }
1920
let requestURLStr: String = "https://itunes.apple.com/lookup?bundleId=\(bundleId)" // might need country code
2021
return .init(string: requestURLStr)
2122
}
23+
// The App Store lookup URL may fail if the app is not available in all countries.
24+
// prefer this
25+
internal static var getRequestURL(countryCode = Locale.current.regionCode ?? "US") -> URL? {
26+
guard let bundleId = Bundle.identifier else { return nil }
27+
let requestURLStr = "https://itunes.apple.com/lookup?bundleId=\(bundleId)&country=\(countryCode)"
28+
return URL(string: requestURLStr)
29+
}
2230
}
2331
/**
2432
* Typealias

Sources/UpgradeAlert/UpgradeAlert.swift

+13
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ extension UpgradeAlert {
100100

101101
throw NSError(domain: "no app info", code: 0) // If there is no app info, throw an NSError with the description "no app info"
102102
}
103+
104+
// ⚠️️ new
105+
// The App Store metadata may be updated before the app binary is available, causing users to see an update prompt before they can download the update.
106+
// Check the currentVersionReleaseDate from the App Store response and delay prompting users if the update is very recent.
107+
let dateFormatter = ISO8601DateFormatter()
108+
if let releaseDate = dateFormatter.date(from: appInfo.currentVersionReleaseDate) {
109+
let daysSinceRelease = Calendar.current.dateComponents([.day], from: releaseDate, to: Date()).day ?? 0
110+
if daysSinceRelease < 1 {
111+
completion(.success(())) // Do not prompt the user yet
112+
return
113+
}
114+
}
115+
103116
completion?(info, nil)
104117
} catch {
105118
// Handle potential errors during data fetching and decoding

Sources/UpgradeAlert/ext/Bundle+Ext.swift

+12-4
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,33 @@ extension Bundle {
88
* - Description: Retrieves the name of the main bundle of the application.
99
* Example: `let appName = Bundle.name`
1010
*/
11-
internal static let name: String? = Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as? String
11+
internal static var name: String? {
12+
main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as? String
13+
}
1214
/**
1315
* The version of the main bundle.
1416
* - Description: Retrieves the version number of the main bundle of the application.
1517
* Example: `let appVersion = Bundle.version`
1618
*/
17-
internal static let version: String? = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
19+
internal static var version: String? {
20+
main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
21+
}
1822
/**
1923
* The build number of the main bundle.
2024
* - Description: Retrieves the build number of the main bundle of the application.
2125
* Example: `let buildNumber = Bundle.build`
2226
*/
23-
internal static let build: String? = Bundle.main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String
27+
internal static var build: String? {
28+
main.object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String
29+
}
2430
/**
2531
* The identifier of the main bundle.
2632
* - Description: Retrieves the unique identifier of the main bundle of the application.
2733
* Example: `let bundleIdentifier = Bundle.identifier`
2834
*/
29-
internal static let identifier: String? = Bundle.main.object(forInfoDictionaryKey: kCFBundleIdentifierKey as String) as? String
35+
internal static var identifier: String? {
36+
main.object(forInfoDictionaryKey: kCFBundleIdentifierKey as String) as? String
37+
}
3038
/**
3139
* Tests if an app is in beta
3240
* - Abstract: determines if beta from receipt

Sources/UpgradeAlert/ext/UIAlertController+Ext.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ extension UIAlertController {
1010
* Fix: throw error if vc is not available?
1111
*/
1212
internal func present() {
13-
Self.presentedOrRootVC?.present(self, animated: true, completion: nil)
13+
guard let viewController = Self.presentedOrRootVC else {
14+
print("Error: No view controller available to present the alert.")
15+
return
16+
}
17+
viewController.present(self, animated: true, completion: nil)
1418
}
1519
}
1620
/**

Sources/UpgradeAlert/helper/AppInfo.swift

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public struct AppInfo: Decodable {
3030
* review the app.
3131
*/
3232
public let trackViewUrl: String
33+
// ⚠️️ new
34+
public let currentVersionReleaseDate: String
3335
/**
3436
* Initializes a new instance of `AppInfo`.
3537
* - Description: Initializes a new instance of `AppInfo` with the specified

Sources/UpgradeAlert/util/ComparisonResult.swift

+17-12
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,23 @@ extension ComparisonResult {
3838
* - appStore: "1.2.9" The version of the app in the App Store.
3939
*/
4040
static func compareVersion(current: String, appStore: String) -> ComparisonResult {
41-
let versionCompare/*: ComparisonResult*/ = current.compare(appStore, options: .numeric)
42-
switch versionCompare {
43-
case .orderedSame:// The current version is the same as the App Store version.
44-
//print("same version")
45-
return .compatible
46-
case .orderedAscending:// The current version is older than the App Store version.
47-
// will execute the code here
48-
// print("ask user to update")
49-
return .requiresUpgrade
50-
case .orderedDescending: // app-store listing hasn't updated yet, The current version is newer than the App Store version. This is an unexpected case.
51-
// execute if current > appStore
52-
// print("don't expect happen...")
41+
// let versionCompare/*: ComparisonResult*/ = current.compare(appStore, options: .numeric)
42+
// switch versionCompare {
43+
// case .orderedSame:// The current version is the same as the App Store version.
44+
// //print("same version")
45+
// return .compatible
46+
// case .orderedAscending:// The current version is older than the App Store version.
47+
// // will execute the code here
48+
// // print("ask user to update")
49+
// return .requiresUpgrade
50+
// case .orderedDescending: // app-store listing hasn't updated yet, The current version is newer than the App Store version. This is an unexpected case.
51+
// // execute if current > appStore
52+
// // print("don't expect happen...")
53+
// return .compatible
54+
// }
55+
if current.compare(appStore, options: .numeric) == .orderedAscending {
56+
return .requiresUpgrade
57+
} else {
5358
return .compatible
5459
}
5560
}

Tests/UpgradeAlertTests/UpgradeAlertTests.swift

+19
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,23 @@ final class UpgradeAlertTests: XCTestCase {
3030
}
3131
}
3232
}
33+
// ⚠️️ untested
34+
func testGetAppInfoWithInvalidURL() {
35+
// Temporarily override the requestURL to be invalid
36+
let originalURL = UpgradeAlert.requestURL
37+
UpgradeAlert.requestURL = nil
38+
let expectation = self.expectation(description: "Completion handler called")
39+
UpgradeAlert.getAppInfo { result in
40+
switch result {
41+
case .success:
42+
XCTFail("Expected failure when URL is invalid")
43+
case .failure(let error):
44+
XCTAssertEqual(error, .invalidURL)
45+
}
46+
expectation.fulfill()
47+
}
48+
waitForExpectations(timeout: 5, handler: nil)
49+
// Restore the original URL
50+
UpgradeAlert.requestURL = originalURL
51+
}
3352
}

0 commit comments

Comments
 (0)