Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxSwift filter Variable array

Hi I am trying to get an understanding of the RxSwift library to write better, functional code.

Currently I am stuck at a very basic problem. Lets say I got this variables of type Variable<[CiteModel?]>:

var allCites: Variable<[CiteModel?]> = Variable([])
var shownCites: Variable<[CiteModel?]> = Variable([])

Now I want to filter all cites from allCites array which contain a specific text and add them to shownCites.

This is what I have tried but it does not compile because inside my filter block $0 is [CiteModel?] not CiteModel? what I would expect. Could you explain to me what I did wrong ?

private func filterCitesByQuery(query: String) {
    self.shownCites = self.allCites.asObservable().filter {
        $0?.cite.containsString(query)
    }
}

Error when executing the above code:

Cannot assign value of type 'Observable<[CiteModel?]>' (aka 'Observable<Array<Optional<CiteModel>>>') to type 'Variable<[CiteModel?]>' (aka 'Variable<Array<Optional<CiteModel>>>')
like image 573
dehlen Avatar asked Dec 14 '22 07:12

dehlen


2 Answers

map performs an operation on each values of a sequence. When applying map to an Observable<T>, map will receive T as a parameter to its block.

In the case of Variable<[CiteModel?]>, Observable is the sequence hence T == [CiteModel?].

Because we really want to filter an array of [CiteModel?], you could change the definition of filterCitesByQuery to

private func filterCitesByQuery(query: String) {
    // bag probably needs to be reset here
    allCites.asObservable()
        .map { // map: apply a transformation to $0
        // The desired transformation of $0 is to remove cite that do not contain query 
        $0.filter { $0.cite.containsString(query) }
    }
    .bindTo(shownCites)
    .addDisposableTo(bag)
}

But this snippet is still sub-optimal, as it requires a subscription to allCites to be made, where we don't really want to observe its changes.

A better implementation would be

var allCites: Variable<[CiteModel?]> = Variable([])
var searchQuery: Variable<String> = Variable("")
var shownCites: Observable<[CiteModel?]> = Observable .combineLatest(allCites.asObservable(), searchQuery.asObservable()) {
    allCites, query in
    return allCites.map { cites in cites.filter { $0.cite.containsString(query) } }
}

private func filterCitesByQuery(query: String) {
    searchQuery.value = query
}

What's going on here ?

combineLatest takes the last 2 known values of allCites and searchQuery. When either of those changes, the block is executed. We now can subscribe to shownCites and will get updated values everytime either of the source observable changes.

like image 94
tomahh Avatar answered Dec 27 '22 01:12

tomahh


I got it working with this code:

private func filterCitesByQuery(query: String) {
     self.shownCites.value = self.allCites.value.filter {
         return $0?.cite.containsString(query) ?? false
     }
 }

However i still would like to know which was wrong with original code. So if anyone has an answer for me this would be really appreciated.

like image 30
dehlen Avatar answered Dec 27 '22 02:12

dehlen