Why is my Combine httpMethod post request not working?

Viewed 1099

I am trying to use Combine to perform a POST request. When doing my http request that I used before, my Credentials object comes back with status code 200, so all good. But when I am trying to use the Combine framework, it just returns an error.

finished with error [-999] Error Domain=NSURLErrorDomain Code=-999 "cancelled"

How to solve this issue to make my Combine POST request works as expected?

My Combine request that doesn't work and need to be fixed:

func demoLogin() -> AnyPublisher<Credentials, Error> {
  let url = URL(string: "https://web-api/auth/create-demo-account")!

  var urlRequest = URLRequest(url: url)
  urlRequest.httpMethod = "POST"

  return URLSession.shared
    .dataTaskPublisher(for: urlRequest)
    .receive(on: DispatchQueue.main)
    .map(\.data)
    .decode(
      type: Credentials.self,
      decoder: JSONDecoder())
    .eraseToAnyPublisher()
}

So this is where I sink the value:

final class OnboardingViewModel: ObservableObject {

  private var subscriptions = Set<AnyCancellable>()

  func demoLogin() {
    AuthRequest.shared.demoLogin()
      .sink(
        receiveCompletion: { print($0) },
        receiveValue: {
          print("Receive value:\nLogin: \($0.login)\nToken: \($0.token)") })
      .store(in: &subscriptions)
  }
}

This is the SwiftUI view where the button asks for the call:

struct CredentialsButtons: View {

  @ObservedObject var viewModel = OnboardingViewModel()

  var body: some View {
    VStack {
      Button(action: { self.viewModel.demoLogin() }) {
        Text("Try demo")
          .font(.subheadline)
          .fontWeight(.medium)
          .foregroundColor(.blue)
      }
    }
  }
}

My request that works normally:

  func demoLogin(completion: @escaping (NetworkResult<Credentials>) -> Void) {
    let url = URL(string: "https://web-api/auth/create-demo-account")!

    var urlRequest = URLRequest(url: url)
    urlRequest.httpMethod = "POST"

    let dataTask = authSession.dataTask(with: urlRequest) { data, response, error in
      guard let httpResponse = response as? HTTPURLResponse,
        httpResponse.statusCode == 200,
        let jsonData = data else {
          completion(.failure)
          return
      }
      do {
        let credentials = try JSONDecoder().decode(Credentials.self, from: jsonData)
        completion(.success(credentials))
      }
      catch {
        completion(.failure)
      }
    }
    dataTask.resume()
  }
2 Answers

That is not a valid URL. insert a print into your publisher as follows so you can see the errors:

func demoLogin() -> AnyPublisher<Credentials, Error> {
  let url = URL(string: "https://web-api/auth/create-demo-account")!

  var urlRequest = URLRequest(url: url)
  urlRequest.httpMethod = "POST"

  return URLSession.shared
    .dataTaskPublisher(for: urlRequest)
    .receive(on: DispatchQueue.main)
    .map(\.data)
    .print()
    .decode(
      type: Credentials.self,
      decoder: JSONDecoder())
    .eraseToAnyPublisher()
}

Once you get it to return valid data you can move the print after the decode to report and decoding errors.

The network call is working as expected. The code that was making this error was found in the button where I called the demoLogin() ViewModel method. The thing is that I could not call @State variables at the same time as the ViewModel network call.

The button with an error send:

Button(action: {
  withAnimation {
    self.viewModel.demoLogin()
    self.isLoggedIn.toggle()
    self.enterDemoMode.toggle() }
}) {
  Text("Try demo")
    .font(.subheadline)
    .fontWeight(.medium)
    .foregroundColor(.blue)
}

The error is fixed by replacing the @State variables of the SwiftUI view to @Published ones in my ViewModel and calling them in demoLogin() instead of inside the SwiftUI view button.

Button(action: {
  withAnimation {
    self.viewModel.demoLogin() } // Fixed
}) {
  Text("Try demo")
    .font(.subheadline)
    .fontWeight(.medium)
    .foregroundColor(.blue)
}
Related