How to filter combineLatest to be trigger only when One item changes?

Viewed 4651

I wrote small demo code as following. I made two PublishSubject of different types. as I change any, page triggers I need to get page trigger only when one changes, which of observable_page.

class ViewController: UIViewController {

    func loadData(page: Int, keyword: String) -> Observable<[Int]> {
        let _result = Observable.of([1,2,3,4])
        return _result
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let observable_keyword = PublishSubject<String>()
        let observable_page = PublishSubject<Int>()
        let trigger_tap = PublishSubject<Void>()

        let tapObservable = trigger_tap.debug("trigger_tap", trimOutput: true)
        let stringObservable = observable_keyword.debug("stringObservable", trimOutput: true)
        let pageObservable = observable_page.debug("pageObservable", trimOutput: true)

        let request_call_trigger = Observable.combineLatest(tapObservable, pageObservable)
            .debug("request_call_trigger", trimOutput: true)

        let page = request_call_trigger
            .withLatestFrom(stringObservable) { ($0, $1) }
            .flatMap { ((_, _, page), keyword) in
                Observable.combineLatest(Observable.just(page), self.loadData(page: page, keyword: keyword)) { (pageNumber: $0, movies: $1) }
                    .materialize()
                    .filter { $0.isCompleted == false }
            }
            .share()

        observable_keyword.onNext("breaking bad")
        observable_page.onNext(1)

        trigger_tap.onNext(())

        observable_keyword.onNext("orange is new black")
        observable_keyword.onNext("orange")

    }

    let bag = DisposeBag()
}

I read some option, felt filter or ignore may work here, but as I need their value in next, so confused, how to apply it properly.

2 Answers

If a single trigger is what you're looking for, I think the operator you seek is withLatestFrom :

observable2
  .withLatestFrom(observable1)

Means: Only when observable2 changes - get its latest emitted value together with the latest emitted value of observable1.

edit: If you want both values, you might need to provide a resultsSelector:

observable2
  .withLatestFrom(observable1) { ($0, $1) }

You can use withLatestFrom which as per documentation

Merges two observable sequences into one observable sequence by combining each element from self with the latest element from the second source, if any

in your code request_call_trigger is of type Observable<(Void, Int>) which you combine with the latest value from stringObservable using withLatestFrom. This produces another tuple of type Observable<(((Void, Int), String)>

you can flatMap on the tuple created by using withLatestFrom and pass the page and keyword values to loadData method by using tuple value for page which is $0.1 so no need to use combineLatest inside flatMap.

        let tapObservable = trigger_tap.debug("trigger_tap", trimOutput: true)
        let stringObservable = observable_keyword.debug("stringObservable", trimOutput: true)
        let pageObservable = observable_page.debug("pageObservable", trimOutput: true)

        let request_call_trigger: Observable<(Void, Int)> = Observable.combineLatest(tapObservable, pageObservable)
            .debug("request_call_trigger", trimOutput: true)

        _ = request_call_trigger
            .withLatestFrom(stringObservable) { ($0, $1) }
            .flatMap { [weak self] (request_page_tuple: (Void, Int), keyword: String) -> Observable<Event<[Int]>> in
                guard let strongSelf = self else {
                    return Observable.empty()
                }
                return strongSelf
                    .loadData(page: request_page_tuple.1, keyword: keyword)
                    .materialize()
                    .filter { !$0.isCompleted }
            }
            .share()
Related