How to properly use switchLatest to switch between search results and empty table state?

I am new to RxSwift and am trying to handle the task of switching between empty state and full autocomplete search state.

I have one driver that reacts to text text, changes length> 0 and makes network queries and another besides filters on blank search queries and just populates the table with Favorites. I originally used merge () on two observables, but the problem was that quickly clearing the text would show the favorites, but when the last fetch request returned it would merge and discard the empty state.

I tried to switch to switchLatest (), hoping it would cancel the previous fetch request when the final clearResult was fired, but this observable never fires at all and I got a bit stuck. Any help would be greatly appreciated!

let queryFetchResultViewModel: Driver<[NewSearchResultViewModel]>  = queryText
                  .filter { $0.utf8.count > 0 }
                  .throttle(0.5, scheduler: MainScheduler.instance)
                  .flatMapLatest { query in
                      appContext.placeStore.fetchPredictions(withQuery: query)
                  }
                  .map { $0.map { NewSearchResultViewModel.prediction(prediction: $0) } }
                  .asDriver(onErrorJustReturn: [])

let queryClearResultViewModel: Driver<[NewSearchResultViewModel]> = queryText
                      .filter { $0.utf8.count == 0 }
                      .withLatestFrom(favoritePlaces.asObservable())
                      .map { places in places.map(NewSearchResultViewModel.place) }
                      .asDriver(onErrorJustReturn: [])

searchResultViewModel = Driver.of(queryFetchResultViewModel, queryClearResultViewModel).switchLatest()

      

+3


source to share


1 answer


You would expect an empty line to affect the output of a stream that explicitly filters out empty lines. This is why you have this problem.

- EDIT -

To better explain above ... Suppose the user types in a character and then deletes it. In the source code. Here's what happens: the character enters filter

for queryClearResultViewModel

and gets filtered. He will also go through the chain for queryFetchResultViewModel

and choose a prediction. Then the user removes the character that will go into the filter for queryFetchResultViewModel

and stop while he goes all the way through queryClearResultViewModel

and searchResultViewModel

gets favorite places ...

Then the network request is completed, which passes the prediction results to searchResultViewModel

.

At this point it switchLatest

will turn off queryClearResultViewModel

(it will stop listening) and display the query results. He stopped listening to the pure stream of results, so there will be no more favorites.

- END EDIT -



Here's some code that will do what you want:

searchResultViewModel = queryText
    .throttle(0.5, scheduler: MainScheduler.instance)
    .flatMapLatest { query -> Observable<[NewSearchResultViewModel]> in
        if query.isEmpty {
            return favoritePlaces.asObservable()
                .map { $0.map(NewSearchResultViewModel.place) }
        }
        else {
            return appContext.placeStore.fetchPredictions(withQuery: query)
                .map { $0.map { NewSearchResultViewModel.prediction(prediction: $0) } }
        }
    }
    .asDriver(onErrorJustReturn: [])

      

The above code works because it flatMapLatest

cancels the call fetchPredictions(withQuery:)

in flight when a new request arrives at it. Even if this request is empty.

If you are married to split queryText

into two streams and then re-merge them, then all you have to do is allow queryFetchResultViewModel

the empty text to be processed by emitting Observable.empty()

when an empty request comes in. But even there, you may run into race problems because sampling is throttled and clarity is not. So you might run into a situation where the fetch starts, then there is an empty query showing the favorites, then the query finishes that displays the results, then the fetch thread gets a (deferred) message and does nothing, since the query has already finished. thus, you will have to throttle the transparent flow as well as the sample flow ...

So something like this (I used debounce because I think it works better for this kind of thing):

let debouncedQuery = queryText.debounce(0.5, scheduler: MainScheduler.instance)

let queryFetchResultViewModel = debouncedQuery
    .flatMapLatest { query -> Observable<[String]> in
        guard !query.isEmpty else { return Observable.empty() }
        return appContext.placeStore.fetchPredictions(withQuery: query)
    }
    .map { $0.map { NewSearchResultViewModel.prediction(prediction: $0) } }
    .asDriver(onErrorJustReturn: [])

let queryClearResultViewModel = debouncedQuery
    .filter { $0.isEmpty }
    .withLatestFrom(favoritePlaces.asObservable())
    .map { $0.map(NewSearchResultViewModel.place) }
    .asDriver(onErrorJustReturn: [])

searchResultViewModel = Driver.merge(queryFetchResultViewModel, queryClearResultViewModel)

      

+1


source







All Articles