0

Hei, I have a SwiftUI with its viewModel. When the user taps on the Login button I run the viewModel.registerAccount function.

func registerAccount() {
    authService.registerAccount(params: params)
        .sink { (dataResponse) in
            if let error = dataResponse.error {
                //
            } else {
                //
            }
        }.store(in: &cancellableSet)
}

In this function I make a call to my AuthService where I want to save some values and change the response.

class AuthService {
    private let apiClient: APIClient = APIClient()
    private var guid: String?

    func registerAccount(params: [String: Any]) -> AnyPublisher<DataResponse<RegisterAccountResponse, Error>, Never> {
        let registerRequest = RegisterAccountRequest(params: params) // (url + method + params + ...)
        let apiRequest = apiClient.request(request: registerRequest)
        // i would like to read response, save guid
        // change response type and return only Error?
        return apiRequest
    }
}

Also the APIClient definition.

class APIClient {
    private let session: Session = Session(configuration: URLSessionConfiguration.default)
    func request<T: APIRequest>(request: T) -> AnyPublisher<DataResponse<T.Response, Error>, Never> {
        request
            .dataRequest(session: session)
            .publishDecodable(type: T.Response.self)
            .map { response in
                response.mapError { error in
                    return error
                }
            }
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}

But this doesn't solve my needs. What would I like is to know if there is a way to read the response in AuthService, and return a response of type Error?.

At one point I had this in mind, to listed also in AuthService but I want to change the response and send only an object of format Error?.

func registerAccount(params: [String: Any]) -> 
AnyPublisher<DataResponse<RegisterAccountResponse, Error>, Never> {
    let registerRequest = RegisterAccountRequest(params: params)
    let apiRequest = apiClient.request(request: registerRequest)
        
    apiRequest.sink { dataResponse in
        if let guid = dataResponse.value?.guidFlow {
            self.guid = guid
        }
    }.store(in: &cancellableSet)
        
    return apiRequest
}

I also thought at this, but I was looking for a more Combine/SwiftUI approach.

func registerAccount(params: [String: Any], completion: @escaping (Error?) -> Void) {
    let registerRequest = RegisterAccountRequest(params: params)
    let apiRequest = apiClient.request(request: registerRequest)
    
    apiRequest.sink { dataResponse in
        if let value = dataResponse.value {
            // save guid and return nill or backend error (format Error)
        } else {
            completion(dataResponse.error)
        }
    }.store(in: &cancellableSet)
}
Alexandru Vasiliu
  • 534
  • 1
  • 4
  • 18

1 Answers1

1

The first thing I find odd is that your APIClient.request appears to be returning an AnyPublisher<DataResponse<RegisterAccountResponse, Error>, Never>. I would expect it to return a AnyPublisher<RegisterAccountResponse, Error> -- a stream that either reports a response or an error.

If you have the ability to change the APIClient I'd change that. If you don't have control over that code, we can work around it with tryMap.

It really seems that you want registerAccount on AuthService to return a publisher that either emits a single GUID, or an error. That might look like this:

class AuthService {
    private let apiClient = APIClient()

    func registerAccount(params: [String: Any]) -> AnyPublisher<UUID?, Error> {
        let registerRequest = RegisterAccountRequest(params: params)

        return apiClient.request(request: registerRequest)
            .tryMap { (response: DataResponse<RegisterAccountResponse, Error>) throws -> UUID? in
                switch response {
                    case .success(let registerAccountResponse) :
                        return registerAccountResponse.value?.guidFlow
                    case .failure(let error) :
                        throw error
                }
            }.eraseToAnyPublisher()
    }
}
Scott Thompson
  • 22,629
  • 4
  • 32
  • 34
  • `func request(request: T) -> AnyPublisher { session.request(request.url, method: request.httpMethod, ...) .publishDecodable(type: T.Response.self) .map { response in response.mapError { error in return error } } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() }` – Alexandru Vasiliu Oct 13 '21 at 11:24
  • I tried changing the method APIClient method but I get `Cannot convert return expression of type 'AnyPublisher.Failure>' (aka 'AnyPublisher') to return type 'AnyPublisher'`. Maybe you can help me downcast it from that DataRequest type to the response type you mentioned. – Alexandru Vasiliu Oct 13 '21 at 11:24
  • I don't know what type `publishDecodable` is returning. Google suggests it might have something to do with Alamofire? If so, and if the doocumentation aligns with your code, then I would take the `map` operation out of your `APIClient`. Just let it return a `DataResponse`... though from the type signatures, it looks like your `RegisterAccountResponse` Failure subtype is `Never` which implies that response can never fail. I'm afraid the deeper we get into your particular type, and the libraries you rely on, the less help I'll be. – Scott Thompson Oct 13 '21 at 13:58