Model layer
class MyWebService {
static let shared = MyWebService()
// URLSession instance / configuration
// Environments (dev, qa, prod, test / mocks)
// Requests and token management
var token: String? = nil
func request<T>(/* path, method, query, payload*/) async throws -> T { ... }
func requestAccess(username: String, password: String) async throws { ... }
func revokeAccess() async throws { ... }
}
// Other names: User, UserStore, CurrentUser, ...
class Account: ObservableObject {
@Published var isLogged: Bool = false
@Published var error: Error? = nil
struct Profile {
let name: String
let avatar: URL?
}
@Published var profile: Profile? = nil
enum SignInError: Error {
case mandatoryEmail
case invalidEmail
case mandatoryPassword
case userNotFound
case userActivationNeeded
}
func signIn(email: String, password: String) async {
// Validation
if email.isEmpty {
error = SignInError.mandatoryEmail
return
} else if email.isEmail {
error = SignInError.invalidEmail
return
}
if password.isEmpty {
error = SignInError.mandatoryPassword
return
}
// Submit
do {
try await MyWebService.shared.requestAccess(username: email,
password: password)
isLogged = true
error = nil
} catch HTTPError.forbidden {
error = SignInError.userActivationNeeded
} catch HTTPError.notFound {
error = SignInError.userNotFound
} catch {
self.error = error
}
}
func signOut() async {
// Ignore any error
try? await MyWebService.shared.revokeAccess()
isLogged = false
}
func loadProfile() async {
do {
profile = try await MyWebService.shared.request(...)
error = nil
} catch {
self.error = error
}
}
}
View layer
@main
struct MyApp: App {
@StateObject var account = Account()
var body: some Scene {
WindowGroup {
Group {
if account.isLogged {
TabView { ... }
.task {
async account.loadProfile()
}
} else {
SignInView()
}
}
.environmentObject(account)
}
}
}
struct SignInView: View {
@EnvironmentObject var account: Account
@State private var email: String = ""
@State private var password: String = ""
// Text fields, button, error exception
var body: some View {
ScrollView { ... }
}
func signIn() {
Task {
await account.signIn(email: email,
password: password)
}
}
}