Complete Challenge 3

> Our app silently fails when errors occur during biometric authentication. Add code to show those errors in an alert. But be careful: you can only add one alert() modifier to each view.
This commit is contained in:
CypherPoet 2019-12-18 22:22:01 -06:00
parent 53dc10ab4a
commit 74ac5d349b
4 changed files with 43 additions and 25 deletions

View File

@ -14,7 +14,7 @@ import LocalAuthentication
struct AppState {
var authenticationService: AuthenticatingService
var authenticationService: AuthenticationService
var locationCollectionsState: LocationCollectionsState
var wikiPagesState: WikiPagesState
@ -24,7 +24,7 @@ struct AppState {
init(
authenticationService: AuthenticatingService = AuthenticationService(laContextType: LAContext.self),
authenticationService: AuthenticationService = AuthenticationService(laContextType: LAContext.self),
locationCollectionsState: LocationCollectionsState = .init(),
wikiPagesState: WikiPagesState = .init()
) {

View File

@ -24,19 +24,12 @@ extension LAContext: LAContextType {}
protocol AuthenticatingService {
/// - Parameter reason: The app-provided reason for requesting authentication,
/// which displays in the authentication dialog presented to the user.
func authenticate(reason: String) -> AnyPublisher<Void, Error>
}
final class AuthenticationService: AuthenticatingService {
class AuthenticationService {
static let authReason = "Please authenticate to unlock this app and access saved locations."
enum Error: Swift.Error {
enum Error: Swift.Error, Identifiable {
var id: String { self.localizedDescription }
case noBiometricsEnabled(Swift.Error?)
case evaluationFailed(Swift.Error?)
}
@ -46,12 +39,14 @@ final class AuthenticationService: AuthenticatingService {
private var context: LAContextType?
init(laContextType: LAContextType.Type) {
init(laContextType: LAContextType.Type = LAContext.self) {
self.laContextType = laContextType
}
func authenticate(reason: String) -> AnyPublisher<Void, Swift.Error> {
/// - Parameter reason: The app-provided reason for requesting authentication,
/// which displays in the authentication dialog presented to the user.
func authenticate(reason: String) -> AnyPublisher<Void, Error> {
let context = laContextType.init()
self.context = context
@ -94,8 +89,8 @@ final class AuthenticationService: AuthenticatingService {
extension SampleData {
class AuthService: AuthenticatingService {
func authenticate(reason: String) -> AnyPublisher<Void, Error> {
class AuthService: AuthenticationService {
override func authenticate(reason: String) -> AnyPublisher<Void, Error> {
Empty().eraseToAnyPublisher()
}
}

View File

@ -30,6 +30,7 @@ extension LocationCollectionsContainerView {
}
}
.navigationBarTitle("PlaceCase")
.alert(item: $viewModel.authenticationError) { _ in self.authenticationErrorAlert }
}
}
@ -70,6 +71,15 @@ extension LocationCollectionsContainerView {
}
.padding()
}
private var authenticationErrorAlert: Alert {
.init(
title: Text(viewModel.authenticationErrorAlertTitle),
message: Text(viewModel.authenticationErrorAlertBody),
dismissButton: .default(Text("OK"))
)
}
}

View File

@ -14,20 +14,19 @@ import Combine
final class LocationCollectionsContainerViewModel: ObservableObject {
private var subscriptions = Set<AnyCancellable>()
private let authService: AuthenticatingService
private let authService: AuthenticationService
// MARK: - Published Properties
@Published var isAuthenticated: Bool = false
@Published var authenticationError: AuthenticationService.Error? = nil
// MARK: - Init
init(
authService: AuthenticatingService
authService: AuthenticationService
) {
self.authService = authService
setupSubscribers()
}
}
@ -49,7 +48,7 @@ extension LocationCollectionsContainerViewModel {
switch completion {
case .failure(let error):
DispatchQueue.main.async { [weak self] in
print("Authentication failed: \(error)")
self?.authenticationError = error
self?.isAuthenticated = false
}
default:
@ -67,11 +66,25 @@ extension LocationCollectionsContainerViewModel {
}
}
// MARK: - Computed
extension LocationCollectionsContainerViewModel {
var authenticationErrorAlertTitle: String { "Authentication Failed" }
var authenticationErrorAlertBody: String {
guard let error = authenticationError else { return "" }
switch error {
case .noBiometricsEnabled:
return "No Biomentric authentication is enabled on this device."
case .evaluationFailed(_):
return "An error occured while attempting evaluation."
}
}
}
// MARK: - Private Helpers
private extension LocationCollectionsContainerViewModel {
func setupSubscribers() {
}
}