UIView tap gesture also neglects touchUpOutside

Therefore there’s no need to use button to neglect touchhUpOutside case.

        rightButton?.rx.tapGesture().when(.recognized)
            .asPublisherIgnoringError()
            .sink(receiveValue: { [weak self] _ in
                self?.onEvent?(.tapRightButton)
            })
            .store(in: &subscriptions)

filterNil on Single

  1. If you want to filter “Value”
return currencyRateRepository.getRate(base: currency, symbol: .krw, date: date)
            .filterNil() 
            .map { Mutation.setFetchedCurrencyRate($0) }
            .asObservable()
            .catchAndReturn(Mutation.setCurrencyRate(defaultCurrencyRate))

This doesn’t compile. error: Value of type ‘Single<Double?>’ (aka ‘PrimitiveSequence<SingleTrait, Optional<Double>>’) has no member ‘filterNil’

filterNil is method of ObservableType

public extension ObservableType where Element: OptionalType {
  /**
   Unwraps and filters out `nil` elements.

   - returns: `Observable` of source `Observable`'s elements, with `nil` elements filtered out.
   */

  func filterNil() -> Observable<Element.Wrapped> {
    return flatMap { element -> Observable<Element.Wrapped> in
      guard let value = element.value else {
        return Observable<Element.Wrapped>.empty()
      }
      return Observable<Element.Wrapped>.just(value)
    }
  }

2. So you can use filter on the Value

return currencyRateRepository.getRate(base: currency, symbol: .krw, date: date)
            .filter { $0 != nil }
            .map { Mutation.setFetchedCurrencyRate($0) }
            .asObservable()
            .catchAndReturn(Mutation.setCurrencyRate(defaultCurrencyRate))

Or make the value as Observable and then use filterNil

return currencyRateRepository.getRate(base: currency, symbol: .krw, date: date)
            .asObservable()
           .filterNil()
            .map { Mutation.setFetchedCurrencyRate($0) }
            .catchAndReturn(Mutation.setCurrencyRate(defaultCurrencyRate))

3. But if it’s filtered, the Single completes right after without any error. It could be better to make error when it’s filtered.

return currencyRateRepository.getRate(base: currency, symbol: .krw, date: date)
            .asObservable()
            .map {
                if $0 == nil {
                    throw CurrencyRateError.noData
                }
                return Mutation.setFetchedCurrencyRate($0)
            }
            .catchAndReturn(Mutation.setCurrencyRate(defaultCurrencyRate))
    }


enum CurrencyRateError: Error {
    case noData
}

How to chain other observable

  1. Use concat. merge doesn’t make the elements in order.
return .concat(
                    getInitialData(),
                    expandPositionIfCountIsOne()
                )

2. But if codes like this, it doesn’t work

private func expandPositionIfCountIsOne() -> Observable<Mutation> {
        if currentState.positions?.count == 1,
           let userStock = currentState.positions?.first {
            return expandPosition(userStock: userStock)
        }
        return .empty()
    }

Because the condition line executes right after, not in observable not after getInitialData. So it always return empty()

3. So the codes need to be like this

private func expandPositionIfCountIsOne() -> Observable<Mutation> {
        return Observable<Void>.just(())
            .observe(on: MainScheduler.instance)
            .flatMap { [weak self] _ in
                guard let self = self,
                      self.currentState.positions?.count == 1,
                      let userStock = self.currentState.positions?.first
                else {
                    return Observable<Mutation>.empty()
                }
                return self.expandPosition(userStock: userStock)
            }
    }

Chain the condition inside the observable. Because it reads currentState, it’s better to use MainScheduler.instance

4. What if just call the repository data without chaining into the observable

positionRepository.getUserStock(stockID: stockID, positionID: positionID)
                .map { Mutation.setUserStockDetail(positionID, $0) }
                .catch { error in
                    Toast.somethingWentWrong(error: error)
                }

This doesn’t do anything. Because the result is not observed.

5. Map -> Mutation, flatMap -> Observable

/**
     Projects each element of an observable sequence into a new form.
     
     - seealso: [map operator on reactivex.io](http://reactivex.io/documentation/operators/map.html)
     
     - parameter transform: A transform function to apply to each source element.
     - returns: An observable sequence whose elements are the result of invoking the transform function on each element of source.
     
     */
    public func map<Result>(_ transform: @escaping (Element) throws -> Result)
        -> Single<Result> {
            return Single(raw: self.primitiveSequence.source.map(transform))
    }

/**
     Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence.
     
     - seealso: [flatMap operator on reactivex.io](http://reactivex.io/documentation/operators/flatmap.html)
     
     - parameter selector: A transform function to apply to each element.
     - returns: An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence.
     */
    public func flatMap<Result>(_ selector: @escaping (Element) throws -> Single<Result>)
        -> Single<Result> {
            return Single<Result>(raw: self.primitiveSequence.source.flatMap(selector))
    }

send log after getting server response

func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .initialize:
            return getServerData()
                .do(afterCompleted: { [weak self] _ in
                    logAppEvent(serverData)
                })
...
}

private func getServerData() -> Observable<Mutation> {
        return serverRepository.getData()
            .map { Mutation.setUserStockDetail($0)}
            .asObservable()
    }

If you use onNext, logAppEvent is called before getting data. And if you use merge or concat, onNext is called by each element. If you want to log once, use afterCompleted.