Use Alamofire with async and await
Tags: swift
Published: 2023-01-27 06:44:56

[Alamofire](https://github.com/Alamofire/Alamofire) is a swift http request lib. By default, we need use completion handler to handle response. Today we are going to find out how to use it with async and await.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/11nokvfxiyifkh5ktg6o.png)

And we will learn how to achieve this by fetching a **appliance list** from [this url](https://random-data-api.com/api/v2/appliances?size=4). It's provided by [random-data-api.com](https://random-data-api.com/documentation) 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3m31kec8g6a3slajfh6y.png)

### Install Alamofire

First we should create an new iOS project in XCode, and install Alamofire in our project with Swift package manger

```url
https://github.com/Alamofire/Alamofire.git
```

### Define Model Appliance

Models/Appliance.swift

```swift
struct Appliance: Identifiable, Codable {
    let id: Int
    let uid: String
    let brand: String
    let equipment: String
}
```

### Create Network Manager

Network/NetworkManager.swift

```swift
import Foundation
import Alamofire

private let API_BASE_URL = "https://random-data-api.com"

actor NetworkManager: GlobalActor {
    static let shared = NetworkManager()
    private init() {}

    private let maxWaitTime = 15.0
    var commonHeaders: HTTPHeaders = [
        "user_id": "123",
        "token": "xxx-xx"
    ]

    func get(path: String, parameters: Parameters?) async throws -> Data {
       // You must resume the continuation exactly once
        return try await withCheckedThrowingContinuation { continuation in
            AF.request(
                API_BASE_URL + path,
                parameters: parameters,
                headers: commonHeaders,
                requestModifier: { $0.timeoutInterval = self.maxWaitTime }
            )
            .responseData { response in
                switch(response.result) {
                case let .success(data):
                    continuation.resume(returning: data)
                case let .failure(error):
                    continuation.resume(throwing: self.handleError(error: error))
                }
            }
        }
    }

    private func handleError(error: AFError) -> Error {
        if let underlyingError = error.underlyingError {
            let nserror = underlyingError as NSError
            let code = nserror.code
            if code == NSURLErrorNotConnectedToInternet ||
                code == NSURLErrorTimedOut ||
                code == NSURLErrorInternationalRoamingOff ||
                code == NSURLErrorDataNotAllowed ||
                code == NSURLErrorCannotFindHost ||
                code == NSURLErrorCannotConnectToHost ||
                code == NSURLErrorNetworkConnectionLost
            {
                var userInfo = nserror.userInfo
                userInfo[NSLocalizedDescriptionKey] = "Unable to connect to the server"
                let currentError = NSError(
                    domain: nserror.domain,
                    code: code,
                    userInfo: userInfo
                )
                return currentError
            }
        }
        return error
    }
}
```

### Create Network API

Network/NetworkAPI.swift

```swift
import Foundation

class NetworkAPI {
    static func getAppliances() async -> [Appliance]? {
        do {
            let data = try await NetworkManager.shared.get(
                path: "/api/v2/appliances?size=4", parameters: nil
            )
            let result: [Appliance] = try self.parseData(data: data)
            return result
        } catch let error {
            print(error.localizedDescription)
            return nil
        }
    }

    private static func parseData<T: Decodable>(data: Data) throws -> T{
        guard let decodedData = try? JSONDecoder().decode(T.self, from: data)
        else {
            throw NSError(
                domain: "NetworkAPIError",
                code: 3,
                userInfo: [NSLocalizedDescriptionKey: "JSON decode error"]
            )
        }
        return decodedData
    }
}
```

### Create view model for ContentView

ContentView.swift

```swift
class ContentViewViewModel: ObservableObject {
    @MainActor @Published var errorMessage = ""
    @MainActor @Published var appliances: [Appliance] = []

    func fetchAppliances() async {
        await MainActor.run {
            self.errorMessage = ""
        }
        if let res = await NetworkAPI.getAppliances() {
            await MainActor.run {
                self.appliances = res
            }
        } else {
            await MainActor.run {
                self.errorMessage = "Fetch data failed"
            }
        }
    }
}
```

### Render data in ContentView

ContentView.swift

```swift
struct ContentView: View {
    @StateObject var viewModel = ContentViewViewModel()

    var body: some View {
        VStack(spacing: 16) {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            if viewModel.errorMessage != "" {
                Text(viewModel.errorMessage)
            }
            Button("Fetch") {
                Task {
                    await viewModel.fetchAppliances()
                }
            }
            List {
                ForEach(viewModel.appliances, id: \.id) { item in
                    Text("\(item.brand) - \(item.equipment)")
                }
            }
            .listStyle(.inset)
        }
        .padding()
        .onAppear {
            Task {
                await viewModel.fetchAppliances()
            }
        }
    }
}
```

And the job is done. [Source code](https://github.com/arnosolo/AlamofireWithAsyncAwait)