SolvedRxSwift Cannot flatMap a Single trait to a Completable trait

⚠️ If you don't have something to report in the following format, it will probably be easier and faster to ask in the slack channel first. ⚠️

⚠️ Please take you time to fill in the fields below. If we aren't provided with this basic information about your issue we probably won't be able to help you and there won't be much we can do except to close the issue :( ⚠️

If you still want to report issue, please delete above statements before submitting an issue.

Short description of the issue:

Cannot flatMap a Single trait to a Completable trait

Expected outcome:

I expect that the flatMap would let me convert to a Completable trait like in the example below.

What actually happens:

I get the following error:

 'flatMap' produces 'PrimitiveSequence<SingleTrait, R>', not the expected contextual result type 'PrimitiveSequence<SingleTrait, _>'

Self contained code example that reproduces the issue:

func convertExample() -> Completable {
    return Single<String>.create { single in
        single(.success("hello"))
        return Disposables.create()
      }
      .flatMap({ _ -> Completable in
        return Completable.empty()
      })
  }

RxSwift/RxCocoa/RxBlocking/RxTest version/commit

v3.4.0

Platform/Environment

  • iOS
  • macOS
  • tvOS
  • watchOS
  • playgrounds

How easy is to reproduce? (chances of successful reproduce after running the self contained code)

  • easy, 100% repro
  • sometimes, 10%-100%
  • hard, 2% - 10%
  • extremely hard, %0 - 2%

Xcode version:

8.3.1

⚠️ Fields below are optional for general issues or in case those questions aren't related to your issue, but filling them out will increase the chances of getting your issue resolved. ⚠️

Installation method:

  • CocoaPods
  • Carthage
  • Git submodules

I have multiple versions of Xcode installed:
(so we can know if this is a potential cause of your issue)

  • yes (which ones)
  • no

Level of RxSwift knowledge:
(this is so we can understand your level of knowledge
and formulate the response in an appropriate manner)

  • just starting
  • I have a small code base
  • I have a significant code base
36 Answers

✔️Accepted Answer

I absolutely agree with @tarunon.
I encounter that situation almost every day.

Currently I implement that architecture by this way,

func createComplexCompletable() -> Completable {
    return singleA
        .asObservable()
        .flatMap { valueA -> Observable<Never> in
            otherCreateCompletableMethod(arg: valueA) // <- returns Completable
                .asObservable()
        }
        .asCompletable()
}

Looks terrible. 🙃

If I can write

func createComplexCompletable() -> Completable {
    return singleA
        .flatMap { valueA -> Completable in
            otherCreateCompletableMethod(arg: valueA)
        }
}

Looks smart. 😎

Other Answers:

Single should be able to be converted into Maybe and Completable, and Completable only to Maybe (But Maybe cannot be converted to other traits).

I wrote a code something like below and it's working. It takes out the source observable once by asObservable and then convert it to other traits.

extension PrimitiveSequence where TraitType == SingleTrait {
    public func asMaybe() -> PrimitiveSequence<MaybeTrait, Element> {
        return self.asObservable().asMaybe()
    }
    
    public func asCompletable() -> PrimitiveSequence<CompletableTrait, Never> {
        return self.asObservable().flatMap { _ in Observable<Never>.empty() }.asCompletable()
    }
}

extension PrimitiveSequence where TraitType == CompletableTrait, ElementType == Swift.Never {
    public func asMaybe() -> PrimitiveSequence<MaybeTrait, Element> {
        return self.asObservable().asMaybe()
    }
}

I've thought about this a bit, we can do something similar like RxJava did, it makes sense, but it might take a month or two to release it.

In my opinion, that make sense next flatMap overloading.

Source\Closure (T) -> Single<U> (T) -> Maybe<U> (T) -> Completable
Single<T> Single<U> Maybe<U> Completable
Maybe<T> Maybe<U> Maybe<U> Completable

Completable has no next(success), so there is no implementation of flatMap

We often make the function that has 1 argument and return Completable, but if we wish to use this function in PrimitiveSequence chaining, I think there is no way to use this function.
andThen can connect from Single to Completable, but we cannot receive next(success) value.

How do you think? @kzaher

Actually I believe that "flatmapCompletable" is totally necessary. In my case, I receive a stream from which I want to use data (Single), but the stream that goes next must not emit any value (Completable). Example:

private func addToken<T>(request : RequestBuilder<T>) -> Single<RequestBuilder<T>> {
        return Single.create {
            request.addHeaders([TokenRepository.headerKey : TokenRepository.sharedInstance.token!])
            $0(.success(request))
            return Disposables.create()
        }
    }

func secureRequest(request : RequestBuilder<Void>) -> Completable {
        return self.addToken(request: request)
            .flatMap {
                self.executeRequest(request: $0)
        }
    }

func executeRequest(request : RequestBuilder<Void>) -> Completable {
        return Completable.create(subscribe: { (observer) -> Disposable in
            request.execute({ (response, error) in
                    guard response != nil else {
                        let error = error ?? WebServiceError.unknown
                        observer(.error(error))
                        return
                    }
                    observer(.completed)
                })
            return Disposables.create()
        })
    }

I have been using RxJava2 and I think they manage better what is related to the changes between observable types than RxSwift... Single to Maybe, Single to Completable, flatmapCompletable, etc.

More Issues: