Replace completion handlers with async/await. (#407)

This commit is contained in:
Doug
2023-01-05 16:43:41 +00:00
committed by GitHub
parent 51e300642c
commit e4d087a0a4
8 changed files with 70 additions and 109 deletions
@@ -302,7 +302,7 @@ class AppCoordinator: AppCoordinatorProtocol {
.sink { [weak self] callback in .sink { [weak self] callback in
switch callback { switch callback {
case .registeredNotifications(let deviceToken): case .registeredNotifications(let deviceToken):
self?.notificationManager?.register(with: deviceToken, completion: nil) Task { await self?.notificationManager?.register(with: deviceToken) }
case .failedToRegisteredNotifications(let error): case .failedToRegisteredNotifications(let error):
self?.notificationManager?.registrationFailed(with: error) self?.notificationManager?.registrationFailed(with: error)
} }
@@ -467,8 +467,8 @@ extension AppCoordinator: NotificationManagerDelegate {
break break
default: default:
// error or no room proxy // error or no room proxy
service.showLocalNotification(with: "⚠️ " + ElementL10n.dialogTitleError, await service.showLocalNotification(with: "⚠️ " + ElementL10n.dialogTitleError,
subtitle: ElementL10n.a11yErrorSomeMessageNotSent) subtitle: ElementL10n.a11yErrorSomeMessageNotSent)
} }
} }
} }
@@ -29,7 +29,7 @@ class MockNotificationManager: NotificationManagerProtocol {
delegate?.authorizationStatusUpdated(self, granted: false) delegate?.authorizationStatusUpdated(self, granted: false)
} }
func register(with deviceToken: Data, completion: ((Bool) -> Void)? = nil) { } func register(with deviceToken: Data) async -> Bool { false }
func registrationFailed(with error: Error) { } func registrationFailed(with error: Error) { }
@@ -46,30 +46,26 @@ class NotificationManager: NSObject, NotificationManagerProtocol {
options: []) options: [])
notificationCenter.setNotificationCategories([replyCategory]) notificationCenter.setNotificationCategories([replyCategory])
notificationCenter.delegate = self notificationCenter.delegate = self
notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, error in Task {
guard let self else { do {
return let granted = try await notificationCenter.requestAuthorization(options: [.alert, .sound, .badge])
}
if let error {
MXLog.debug("[NotificationManager] request authorization failed: \(error)")
} else {
MXLog.debug("[NotificationManager] permission granted: \(granted)") MXLog.debug("[NotificationManager] permission granted: \(granted)")
DispatchQueue.main.async { await MainActor.run {
self.delegate?.authorizationStatusUpdated(self, granted: granted) self.delegate?.authorizationStatusUpdated(self, granted: granted)
} }
} catch {
MXLog.debug("[NotificationManager] request authorization failed: \(error)")
} }
} }
} }
func register(with deviceToken: Data, completion: ((Bool) -> Void)? = nil) { func register(with deviceToken: Data) async -> Bool {
setPusher(with: deviceToken, await setPusher(with: deviceToken, clientProxy: clientProxy)
clientProxy: clientProxy,
completion: completion)
} }
func registrationFailed(with error: Error) { } func registrationFailed(with error: Error) { }
func showLocalNotification(with title: String, subtitle: String?) { func showLocalNotification(with title: String, subtitle: String?) async {
let content = UNMutableNotificationContent() let content = UNMutableNotificationContent()
content.title = title content.title = title
if let subtitle { if let subtitle {
@@ -78,44 +74,39 @@ class NotificationManager: NSObject, NotificationManagerProtocol {
let request = UNNotificationRequest(identifier: ProcessInfo.processInfo.globallyUniqueString, let request = UNNotificationRequest(identifier: ProcessInfo.processInfo.globallyUniqueString,
content: content, content: content,
trigger: nil) trigger: nil)
notificationCenter.add(request) { error in do {
if let error { try await notificationCenter.add(request)
MXLog.debug("[NotificationManager] show local notification failed: \(error)") MXLog.debug("[NotificationManager] show local notification succeeded")
} else { } catch {
MXLog.debug("[NotificationManager] show local notification succeeded") MXLog.debug("[NotificationManager] show local notification failed: \(error)")
}
} }
} }
private func setPusher(with deviceToken: Data, private func setPusher(with deviceToken: Data, clientProxy: ClientProxyProtocol) async -> Bool {
clientProxy: ClientProxyProtocol, do {
completion: ((Bool) -> Void)?) { try await clientProxy.setPusher(pushkey: deviceToken.base64EncodedString(),
Task { kind: .http,
do { appId: ServiceLocator.shared.settings.pusherAppId,
try await clientProxy.setPusher(pushkey: deviceToken.base64EncodedString(), appDisplayName: "\(InfoPlistReader.target.bundleDisplayName) (iOS)",
kind: .http, deviceDisplayName: UIDevice.current.name,
appId: ServiceLocator.shared.settings.pusherAppId, profileTag: pusherProfileTag(),
appDisplayName: "\(InfoPlistReader.target.bundleDisplayName) (iOS)", lang: Bundle.preferredLanguages.first ?? "en",
deviceDisplayName: UIDevice.current.name, url: ServiceLocator.shared.settings.pushGatewayBaseURL.absoluteString,
profileTag: pusherProfileTag(), format: .eventIdOnly,
lang: Bundle.preferredLanguages.first ?? "en", defaultPayload: [
url: ServiceLocator.shared.settings.pushGatewayBaseURL.absoluteString, "aps": [
format: .eventIdOnly, "mutable-content": 1,
defaultPayload: [ "alert": [
"aps": [ "loc-key": "Notification",
"mutable-content": 1, "loc-args": []
"alert": [
"loc-key": "Notification",
"loc-args": []
]
] ]
]) ]
MXLog.debug("[NotificationManager] set pusher succeeded") ])
completion?(true) MXLog.debug("[NotificationManager] set pusher succeeded")
} catch { return true
MXLog.debug("[NotificationManager] set pusher failed: \(error)") } catch {
completion?(false) MXLog.debug("[NotificationManager] set pusher failed: \(error)")
} return false
} }
} }
@@ -36,7 +36,7 @@ protocol NotificationManagerProtocol {
var delegate: NotificationManagerDelegate? { get set } var delegate: NotificationManagerDelegate? { get set }
func start() func start()
func register(with deviceToken: Data, completion: ((Bool) -> Void)?) func register(with deviceToken: Data) async -> Bool
func registrationFailed(with error: Error) func registrationFailed(with error: Error)
func showLocalNotification(with title: String, subtitle: String?) func showLocalNotification(with title: String, subtitle: String?) async
} }
@@ -19,7 +19,7 @@ import UserNotifications
protocol UserNotificationCenterProtocol: AnyObject { protocol UserNotificationCenterProtocol: AnyObject {
var delegate: UNUserNotificationCenterDelegate? { get set } var delegate: UNUserNotificationCenterDelegate? { get set }
func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)?) func add(_ request: UNNotificationRequest) async throws
func requestAuthorization(options: UNAuthorizationOptions, completionHandler: @escaping (Bool, Error?) -> Void) func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool
func setNotificationCategories(_ categories: Set<UNNotificationCategory>) func setNotificationCategories(_ categories: Set<UNNotificationCategory>)
} }
@@ -33,48 +33,29 @@ final class NotificationManagerTests: XCTestCase {
sut = NotificationManager(clientProxy: clientProxySpy, notificationCenter: notificationCenter) sut = NotificationManager(clientProxy: clientProxySpy, notificationCenter: notificationCenter)
} }
func test_whenRegistered_pusherIsCalled() throws { func test_whenRegistered_pusherIsCalled() async {
let expectation = expectation(description: "Callback happened") _ = await sut.register(with: Data())
sut.register(with: Data(), completion: { _ in
expectation.fulfill()
})
waitForExpectations(timeout: 0.5)
XCTAssertTrue(clientProxySpy.setPusherCalled) XCTAssertTrue(clientProxySpy.setPusherCalled)
} }
func test_whenRegisteredSuccess_completionSuccessIsCalled() throws { func test_whenRegisteredSuccess_completionSuccessIsCalled() async throws {
let expectation = expectation(description: "Callback happened") let success = await sut.register(with: Data())
var success = false
sut.register(with: Data(), completion: { s in
success = s
expectation.fulfill()
})
waitForExpectations(timeout: 0.5)
XCTAssertTrue(success) XCTAssertTrue(success)
} }
func test_whenRegisteredAndPusherThrowsError_completionFalseIsCalled() throws { func test_whenRegisteredAndPusherThrowsError_completionFalseIsCalled() async throws {
let expectation = expectation(description: "Callback happened")
var success = true
enum TestError: Error { enum TestError: Error {
case someError case someError
} }
clientProxySpy.setPusherErrorToThrow = TestError.someError clientProxySpy.setPusherErrorToThrow = TestError.someError
sut.register(with: Data(), completion: { s in let success = await sut.register(with: Data())
success = s
expectation.fulfill()
})
waitForExpectations(timeout: 0.5)
XCTAssertFalse(success) XCTAssertFalse(success)
} }
func test_whenRegistered_pusherIsCalledWithCorrectValues() throws { @MainActor
let expectation = expectation(description: "Callback happened") func test_whenRegistered_pusherIsCalledWithCorrectValues() async throws {
let pushkeyData = Data("1234".utf8) let pushkeyData = Data("1234".utf8)
sut.register(with: pushkeyData, completion: { _ in _ = await sut.register(with: pushkeyData)
expectation.fulfill()
})
waitForExpectations(timeout: 0.5)
XCTAssertEqual(clientProxySpy.setPusherPushkey, pushkeyData.base64EncodedString()) XCTAssertEqual(clientProxySpy.setPusherPushkey, pushkeyData.base64EncodedString())
XCTAssertEqual(clientProxySpy.setPusherAppId, settings?.pusherAppId) XCTAssertEqual(clientProxySpy.setPusherAppId, settings?.pusherAppId)
XCTAssertEqual(clientProxySpy.setPusherKind, .http) XCTAssertEqual(clientProxySpy.setPusherKind, .http)
@@ -98,28 +79,20 @@ final class NotificationManagerTests: XCTestCase {
XCTAssertTrue(actualPayload.isEqual(to: defaultPayload)) XCTAssertTrue(actualPayload.isEqual(to: defaultPayload))
} }
func test_whenRegisteredAndPusherTagNotSetInSettings_tagGeneratedAndSavedInSettings() throws { func test_whenRegisteredAndPusherTagNotSetInSettings_tagGeneratedAndSavedInSettings() async throws {
let expectation = expectation(description: "Callback happened")
settings?.pusherProfileTag = nil settings?.pusherProfileTag = nil
sut.register(with: Data(), completion: { _ in _ = await sut.register(with: Data())
expectation.fulfill()
})
waitForExpectations(timeout: 0.5)
XCTAssertNotNil(settings?.pusherProfileTag) XCTAssertNotNil(settings?.pusherProfileTag)
} }
func test_whenRegisteredAndPusherTagIsSetInSettings_tagNotGenerated() throws { func test_whenRegisteredAndPusherTagIsSetInSettings_tagNotGenerated() async throws {
let expectation = expectation(description: "Callback happened")
settings?.pusherProfileTag = "12345" settings?.pusherProfileTag = "12345"
sut.register(with: Data(), completion: { _ in _ = await sut.register(with: Data())
expectation.fulfill()
})
waitForExpectations(timeout: 0.5)
XCTAssertEqual(settings?.pusherProfileTag, "12345") XCTAssertEqual(settings?.pusherProfileTag, "12345")
} }
func test_whenShowLocalNotification_notificationRequestGetsAdded() throws { func test_whenShowLocalNotification_notificationRequestGetsAdded() async throws {
sut.showLocalNotification(with: "Title", subtitle: "Subtitle") await sut.showLocalNotification(with: "Title", subtitle: "Subtitle")
let request = try XCTUnwrap(notificationCenter.addRequest) let request = try XCTUnwrap(notificationCenter.addRequest)
XCTAssertEqual(request.content.title, "Title") XCTAssertEqual(request.content.title, "Title")
XCTAssertEqual(request.content.subtitle, "Subtitle") XCTAssertEqual(request.content.subtitle, "Subtitle")
@@ -143,21 +116,18 @@ final class NotificationManagerTests: XCTestCase {
XCTAssertTrue(delegate.isEqual(sut)) XCTAssertTrue(delegate.isEqual(sut))
} }
func test_whenStart_requestAuthorizationCalledWithCorrectParams() throws { func test_whenStart_requestAuthorizationCalledWithCorrectParams() async throws {
sut.start() sut.start()
await Task.yield()
XCTAssertEqual(notificationCenter.requestAuthorizationOptions, [.alert, .sound, .badge]) XCTAssertEqual(notificationCenter.requestAuthorizationOptions, [.alert, .sound, .badge])
} }
func test_whenStartAndAuthorizationGranted_delegateCalled() throws { func test_whenStartAndAuthorizationGranted_delegateCalled() async throws {
authorizationStatusWasGranted = false authorizationStatusWasGranted = false
notificationCenter.requestAuthorizationGrantedReturnValue = true notificationCenter.requestAuthorizationGrantedReturnValue = true
sut.delegate = self sut.delegate = self
sut.start() sut.start()
let expectation = expectation(description: "Wait for main thread") try await Task.sleep(for: .milliseconds(100))
DispatchQueue.main.async {
expectation.fulfill()
}
waitForExpectations(timeout: 1, handler: nil)
XCTAssertTrue(authorizationStatusWasGranted) XCTAssertTrue(authorizationStatusWasGranted)
} }
@@ -21,16 +21,15 @@ class UserNotificationCenterSpy: UserNotificationCenterProtocol {
weak var delegate: UNUserNotificationCenterDelegate? weak var delegate: UNUserNotificationCenterDelegate?
var addRequest: UNNotificationRequest? var addRequest: UNNotificationRequest?
func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)?) { func add(_ request: UNNotificationRequest) async throws {
addRequest = request addRequest = request
completionHandler?(nil)
} }
var requestAuthorizationOptions: UNAuthorizationOptions? var requestAuthorizationOptions: UNAuthorizationOptions?
var requestAuthorizationGrantedReturnValue = false var requestAuthorizationGrantedReturnValue = false
func requestAuthorization(options: UNAuthorizationOptions, completionHandler: @escaping (Bool, Error?) -> Void) { func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool {
requestAuthorizationOptions = options requestAuthorizationOptions = options
completionHandler(requestAuthorizationGrantedReturnValue, nil) return requestAuthorizationGrantedReturnValue
} }
var notificationCategoriesValue: Set<UNNotificationCategory>? var notificationCategoriesValue: Set<UNNotificationCategory>?
+1
View File
@@ -0,0 +1 @@
Notification Manager: Replace completion handlers with async/await.