DEV Community

Cover image for Use Alamofire with async and await
Arno Solo
Arno Solo

Posted on

Use Alamofire with async and await

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

And we will learn how to achieve this by fetching a appliance list from this url. It's provided by random-data-api.com

Image description

Install Alamofire

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

https://github.com/Alamofire/Alamofire.git
Enter fullscreen mode Exit fullscreen mode

Define Model Appliance

Models/Appliance.swift

struct Appliance: Identifiable, Codable {
    let id: Int
    let uid: String
    let brand: String
    let equipment: String
}
Enter fullscreen mode Exit fullscreen mode

Create Network Manager

Network/NetworkManager.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
    }
}
Enter fullscreen mode Exit fullscreen mode

Create Network API

Network/NetworkAPI.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
    }
}
Enter fullscreen mode Exit fullscreen mode

Create view model for ContentView

ContentView.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"
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Render data in ContentView

ContentView.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()
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And the job is done. Source code

Top comments (0)