swifturlalamofirerouterurlrequest

How can I use Alamofire Router to organize the API call? [swift/ Alamofire5]


I'm trying to convert my AF request to Router structures for a cleaner project. I'm getting an error for:

Value of protocol type 'Any' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols.

Please help me to fix the code. THANK YOU!

My URL will have a placeholder for username and the password will be sent in body. The response will be Bool (success), username and bearer token.

Under is my AF request:

let username = usernameTextField.text
let password = passwordTextField.text
let loginParams = ["password":"\(password)"]

AF.request("https://example.com/users/\(username)/login", 
            method: .post, 
            parameters: loginParams, 
            encoder: JSONParameterEncoder.default, 
            headers: nil, interceptor: nil).response { response in

                switch response.result {
                case .success:
                    if let data = response.data {

                        do {
                            let userLogin = try JSONDecoder().decode(UsersLogin.self, from: data)

                            if userLogin.success == true {
                                defaults.set(username, forKey: "username")
                                defaults.set(password, forKey: "password")
                                defaults.set(userLogin.token, forKey: "token")
                                print("Successfully get token.")

                            } else {
                                //show alert
                                print("Failed to get token with incorrect login info.")
                            }
                        } catch {
                            print("Error: \(error)")
                        }
                    }

                case .failure(let error):
                    //show alert
                    print("Failed to get token.")
                    print(error.errorDescription as Any)
                }
            }

What I have so far for converting to AF Router structures:

import Foundation
import Alamofire

enum Router: URLRequestConvertible {
    case login(username: String, password: String)

    var method: HTTPMethod {
        switch self {
        case .login:
            return .post
        }
    }

    var path: String {
        switch self {
        case .login(let username):
            return "/users/\(username)/login"
        }
    }

    var parameters: Parameters? {
        switch self {
        case .login(let password):
            return ["password": password]

        }
    }

    // MARK: - URLRequestConvertible
    func asURLRequest() throws -> URLRequest {
        let url = try Constants.ProductionServer.baseURL.asURL()
        var request = URLRequest(url: url.appendingPathComponent(path))

        // HTTP Method
        request.httpMethod = method.rawValue

        // Common Headers
        request.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.acceptType.rawValue)
        request.setValue(ContentType.json.rawValue, forHTTPHeaderField: HTTPHeaderField.contentType.rawValue)

        // Parameters
        switch self {
        case .login(let password):
            request = try JSONParameterEncoder().encode(parameters, into: request) //where I got the error
        }

        return request
    }
}

class APIClient {
        static func login(password: String, username: String, completion: @escaping (Result<UsersLogin, AFError>) -> Void) {
            AF.request(Router.login(username: username, password: password)).responseDecodable { (response: DataResponse<UsersLogin, AFError>) in
                completion(response.result)
            }
        }
    }

LoginViewController Class (where I replaced the AF.request code)

APIClient.login(password: password, username: username) { result in
                    switch result {
                    case .success(let user):
                        print(user)
                    case .failure(let error):
                        print(error.localizedDescription)
                    }

Codable UsersLogin model

struct UsersLogin: Codable {
    let success: Bool
    let username: String
    let token: String?

    enum CodingKeys: String, CodingKey {
        case success = "success"
        case username = "username"
        case token = "token"
    }
}

Solution

  • Took me a while but finally fixed it. I also clean up the code too.

    enum Router: URLRequestConvertible {
        case login([String: String], String) 
    
        var baseURL: URL {
            return URL(string: "https://example.com")!
        }
    
        var method: HTTPMethod {
            switch self {
            case .login:
                return .post
            }
        }
    
        var path: String {
            switch self {
            case .login(_, let username):
                return "/users/\(username)/login"
            }
        }
    
        func asURLRequest() throws -> URLRequest {
            print(path)
            let urlString = baseURL.appendingPathComponent(path).absoluteString.removingPercentEncoding!
            let url = URL(string: urlString)
            var request = URLRequest(url: url!)
            request.method = method
    
            switch self {
            case let .login(parameters, _):
                request = try JSONParameterEncoder().encode(parameters, into: request)
            }
    
            return request
        }
    }
    

    Usage

    let username = usernameTextField.text
    
    AF.request(Router.login(["password": password], username)).responseDecodable(of: UsersLogin.self) { (response) in
    
       if let userLogin = response.value {
          switch userLogin.success {
          case true:
               print("Successfully get token.")
          case false:
               print("Failed to get token with incorrect login info.")
          }
       } else {
          print("Failed to get token.")
       }
    }