Search code examples
iosalamofirereactive-programmingrx-swift

RxSwift merge two api request into one result clears first result


I have a refreshTrigger and BehaviourRelay of items:

var refreshTrigger = PublishSubject<Void>()
var rootItems: BehaviorRelay<[Codable]> = BehaviorRelay(value: [])

Then, I use UITextField to run search query when user enters text:

let queryText = queryTextField.rx.text.orEmpty
        .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
        .distinctUntilChanged()

Finally, I have an observable which combines this observable with additional trigger to refresh manually:

let refreshText = refreshTrigger.withLatestFrom(queryText)
Observable.merge(refreshText, queryText)
        .flatMapLatest { [weak self] query -> Observable<[Codable]> in
            guard let strongSelf = self else { return .empty() }
            
            let ctgs = try strongSelf.getCategories()
                .startWith([])
                .catchErrorJustReturn([])
            
            let itms = try strongSelf.getSearchResults(query)
                .retry(3)
                .startWith([])
                .catchErrorJustReturn([])
            
            return Observable.merge(ctgs, itms)
        }
        .bind(to: rootItems)
        .disposed(by: disposeBag)

As you can see, I want to send 2 requests: fetch categories and items, because I'm displaying them in the same UITableView. It sends both requests at the same time, but first result disappear when the second comes in. But I use merge, so it should work.

Why it doesn't show combined results?

Headers of the getCategories and getSearchResults looks like this:

func getSearchResults(_ text: String) throws -> Observable<[Codable]> 

func getCategories() throws -> Observable<[Codable]> 

they both use alamofire's rx extension to run queries.


Solution

  • Both of your getters return Observable arrays. This means that when the call completes, the observable emits an array of items. When you merge the two Observables, the follow on code can't distinguish between the items from one getter and the items from the other getter. It just sees an array come in (from one of them,) then another array come in (from the other.) In other words, you misunderstood how merge works.

    To achieve the result you want, you should use zip or possibly combineLatest instead of merge. Something like this:

    Observable.zip(ctgs, itms) { $0 + $1 }