Search code examples
iosswiftrx-swift

How can I update tableView from BehaviorRelay observable?


I am trying to do searching in the table view, with the throttle.

Here I have BehaviorRelay in ViewModel

var countryCodes: BehaviorRelay<[CountryDialElement]> = BehaviorRelay<[CountryDialElement]>(value:[])

Here I have BehaviorRelay for the entered text

var searchText: BehaviorRelay<String> = BehaviorRelay<String>(value: "")

Here I have Table View which is binded with Observable from View Model

self.viewModel.params.countryCodes.bind(to: tableView.rx.items(cellIdentifier: "CountryDialTableViewCell")) { index, model, cell in
      let countryCell = cell as! CountryDialTableViewCell
      countryCell.configure(model)
    }.disposed(by: disposeBag)

Here I have Rx binding to UISearchBar in ViewController

searchBar
      .rx
      .text
      .orEmpty
      .debounce(.milliseconds(300), scheduler: MainScheduler.instance)
      .distinctUntilChanged()
      .subscribe { [weak self] query in
        guard
          let query = query.element else { return }
        self?.viewModel.params.searchText.accept(query)
      }
      .disposed(by: disposeBag)

Then in ViewModel, I am trying to filter data and push filtered data to dataSource, to update the tableView.

Observable.combineLatest(params.countryCodes, params.searchText) { items, query in
      return items.filter({ item in
        item.name.lowercased().contains(query.lowercased()) || item.dialCode.lowercased().contains(query.lowercased())
      })
    }.subscribe(onNext: { resultArray in
      self.params.countryCodes.accept(resultArray)
    })
    .disposed(by: disposeBag)

But I am getting this type of Error

Reentrancy anomaly was detected.
This behavior breaks the grammar because there is overlapping between sequence events.
The observable sequence is trying to send an event before sending the previous event has finished.
Interpretation: This could mean that there is some kind of unexpected cyclic dependency in your code, or that the system is not behaving in an expected way.

I am trying to achieve:

  1. Binded table view with an observable array
  2. Type text in the search bar
  3. Filter observable, array
  4. Update observable, reload tableView.

Solution

  • First thing I noticed... You have two BehaviorRelays defined as var. You should always define them with let.

    You haven't posted enough code to demonstrate the error but the fundamental problem, as explained to you in the error message, is that you are breaking the observable grammar because you are pushing data through an Observable while in the middle of pushing data. If allowed, it would form an infinite recursive call that would overflow the stack.

    Don't send an event until after the current event is finished sending. It would help a lot if you didn't use so many Relays...

    You don't say anything about where the items in the array come from which also makes it hard to help...

    Consider something like this:

    Observable.combineLatest(
        searchBar.rx.text.orEmpty
            .debounce(.milliseconds(300), scheduler: MainScheduler.instance)
            .distinctUntilChanged()
            .startWith(""),
        sourceOfItems(),
        resultSelector: { searchTerm, items in
            items.filter { $0.code.contains(searchTerm) }
        }
    )
    .bind(to: tableView.rx.items(cellIdentifier: "CountryDialTableViewCell")) { index, model, cell in
        let countryCell = cell as! CountryDialTableViewCell
        countryCell.configure(model)
    }
    .disposed(by: disposeBag)