1

I am trying to figure out why the last .eraseToAnyPublisher() is giving the aforementioned error, to me it seems all the types are well specified, aren't they?

static func searchUsers(query: String) -> AnyPublisher<[UserViewModel], Never> {
            // prepare URL
            let urlString = "\(baseURL)/search/users?q=\(query)"
            guard let url = URL(string: urlString) else  {
                return Just([]).eraseToAnyPublisher()
            }
            // get handle of native data task publisher
            let publisher = URLSession.shared.dataTaskPublisher(for: url)
                .handleEvents(
                    receiveSubscription: { _ in
                        activityIndicatorPublisher.send(true)
                    }, receiveCompletion: { _ in
                        activityIndicatorPublisher.send(false)
                    }, receiveCancel: {
                        activityIndicatorPublisher.send(false)
                    })
                .tryMap { data, response -> Data in
                    guard let httpResponse = response as? HTTPURLResponse,
                          httpResponse.statusCode == 200 else {
                              throw NetworkError.httpError
                          }
                    print(String(data: data, encoding: .utf8) ?? "")
                    return data
                }
                .decode(type: SearchUserResponse.self, decoder: JSONDecoder())
                .map { $0.items }
                .flatMap({ users in
                    var userViewModels = [UserViewModel]()
                    users.forEach { user in
                        userViewModels.append(contentsOf: UserViewModel(with: user))
                    }
                    return userViewModels
                })
                .catch { err -> Just<[UserViewModel]> in
                    print(err)
                    return Just([])
                }
                .eraseToAnyPublisher() // <-- HERE IS THE ERROR
            return publisher
        }
Fabrizio Prosperi
  • 1,398
  • 4
  • 18
  • 32
  • Take a look in your guard statement at the top of the function. You've got a Just([]) but I don't think you're defining the type there. That could be the problem. – DPrice Jan 26 '22 at 06:10
  • Thank you @DPrice but that is not the problem, the line was there even before I was returning an array of User instances, returning a publisher with an empty array is fine. Should be something else. – Fabrizio Prosperi Jan 26 '22 at 08:13
  • Don't you just need `map` instead of `flatMap`? – LuLuGaGa Jan 26 '22 at 09:42
  • @LuLuGaGa maybe you are right, but can you elaborate? How do I map [User] in [UserViewModel] in this case? – Fabrizio Prosperi Jan 26 '22 at 10:54

1 Answers1

1

Unfortunately with those complex Combine pipelines sometimes compliler errors are displayed on the wrong line. In your case there are two problems, but not where the compiler is pointing.

One being use of flatMap instead of map.

This part of the pipeline:

let publisher = URLSession.shared.dataTaskPublisher(for: url)
    .handleEvents(
        receiveSubscription: { _ in
            activityIndicatorPublisher.send(true)
        }, receiveCompletion: { _ in
            activityIndicatorPublisher.send(false)
        }, receiveCancel: {
            activityIndicatorPublisher.send(false)
        })
        .tryMap { data, response -> Data in
             guard let httpResponse = response as? HTTPURLResponse,
                 httpResponse.statusCode == 200 else {
                 throw NetworkError.httpError
             }
             print(String(data: data, encoding: .utf8) ?? "")
             return data
        }
        .decode(type: SearchUserResponse.self, decoder: JSONDecoder())
        .map { $0.items }

returns Publisher<[User], Error>.

Next you want to transforming that into Publisher<[UserViewModel], Error> for which you need a function map:

func map<T>(_ transform: @escaping (Output) -> T) -> Publishers.Map<Upstream, T>

which transforms one type of Output into another type of Output not flatMap:

func flatMap<T, P>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Self.Output) -> P) -> Publishers.FlatMap<P, Self> where T == P.Output, P : Publisher, Self.Failure == P.Failure

which transforms Output into a new Publisher

The second problem is with append(contentsOf;) which expects a Sequence of elements, in case of single elements you should use append(), but even simpler would be just to map the [User] to [UserViewModel]:

{ users in
    users.map { user in
        UserViewModel(with: user)
    }
}

so the whole function should work with those changes:

static func searchUsers(query: String) -> AnyPublisher<[UserViewModel], Never> {
    // prepare URL
    let urlString = "\(baseURL)/search/users?q=\(query)"
    guard let url = URL(string: urlString) else  {
        return Just([]).eraseToAnyPublisher()
    }
    // get handle of native data task publisher
    let publisher = URLSession.shared.dataTaskPublisher(for: url)
        .handleEvents(
            receiveSubscription: { _ in
                activityIndicatorPublisher.send(true)
            }, receiveCompletion: { _ in
                activityIndicatorPublisher.send(false)
            }, receiveCancel: {
                activityIndicatorPublisher.send(false)
            })
        .tryMap { data, response -> Data in
            guard let httpResponse = response as? HTTPURLResponse,
                  httpResponse.statusCode == 200 else {
                      throw NetworkError.httpError
                  }
            print(String(data: data, encoding: .utf8) ?? "")
            return data
        }
        .decode(type: SearchUserResponse.self, decoder: JSONDecoder())
        .map { $0.items }
        .map { users in
            users.map { user in
                UserViewModel(with: user)
            }
        }
        .catch { err -> Just<[UserViewModel]> in
            print(err)
            return Just([])
        }
        .eraseToAnyPublisher()
    return publisher
}
LuLuGaGa
  • 13,089
  • 6
  • 49
  • 57