RxSwift using MVP subscribe from the Presenter - ios

I have a searchBar to query results from an API. If i query directly from the VC without passing the text to the presenter i get this which works perfectly fine:
searchBar.rx.text
.skip(1)
.throttle(0.5, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest({ (text) -> Observable<[MarvelCharacter]> in
return GetCharacters().execute(offset: 0, name: text)
}).subscribe(onNext: { (characters) in
self.showCharacters(characters: characters)
}).addDisposableTo(disposeBag)
What i really want is to subscribe from GetCharacters() in the presenter.
Could be like this:
ViewController
searchBar.rx.text
.skip(1)
.throttle(0.5, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.map(presenter.searchTextDidChanged)
.subscribe().addDisposableTo(disposeBag)
Presenter
func searchTextDidChanged(text: String) {
getCharacters.execute(offset: offset, name: text)
.subscribe(onNext: { characters in
(self.offset == 0) ?
self.ui.showCharacters(characters: characters) : self.ui.appendCharacters(characters: characters)
}).addDisposableTo(disposeBag)
}
But sometimes i send RxSw (show the result RxSwift) and RxSwZ (empty results), but since i lost the “chain”, getCharacters from the presenter gets executed twice. Then if the 2nd request terminates before the good request i end up in the searchBar with RxSw5 and in the results with RxSwift, how can i deal with this?

Related

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.
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 }

How do I get from Observable<BleHandler.BlePeripheral> to BleHandler.BlePeripheral?

I have a variable that gets me the type Observable<BleHandler.BlePeripheral> after using flatMap on the array.
let scannedPeripheral: Observable<BleHandler.BlePeripheral> = instance.bleScan()
.flatMap{ Observable.from($0)}
But now I need to use that variable in another function that takes BleHandler.BlePeripheral:
instance.bleEstablishConnection(scannedPeripheral: scannedPeripheral)
Obviously it doesn't work. Is there a way to get my Observable<BleHandler.BlePeripheral> to just BleHandler.BlePeripheral
It depends on whether or not the function returns a value and what type of value it returns...
If the function is void and you are just calling it for side effects then:
let disposable = scannedPeripheral
.subscribe(onNext: { instance.bleEstablishConnection(scannedPeripheral: $0) })
If your function has side effects and returns an Observable then:
let returnValue = scannedPeripheral
.flatMap { instance.bleEstablishConnection(scannedPeripheral: $0) }
If the function has no side effects and you are just calling it to transform your value into a different value then:
let returnValue = scannedPeripheral
.map { instance.bleEstablishConnection(scannedPeripheral: $0) }
This last one is unlikely based on the name of the function, but I put it here for completeness.

Call two Swift Combine calls where the second call depends on the result of the first call

I'm new to Swift Combine, so having a small issue
Im trying to call two api calls, and merge the results as the second API call depends on the result from the first call.
Here is the code
return self.gameRepository.fetchGames(forUser: user.id)
.map { games -> AnyPublisher<[Game], ApiError> in
let result = games.map { gameDto -> Game in
let location = self.venueRepository.fetch(by: gameDto.siteId)
.map { $0.mapToModel() }
let game = gameDto.mapToModel(location: location)
return game
}
My error is on line 4 "let location" the compiler complains when I try and pass this through to
line 5
game.mapToModel(location: location)
Cannot convert value of type 'AnyPublisher' to expected argument type Location
The fetch call signature from the repository looks like this
func fetch(by id: String) -> AnyPublisher<LocationDto, ApiError>
So it is correct, but the .map call I use on the result allows the
$0.mapToModel()
to occur, so I have locationDto object that allows me to cast to my Domain model.
Any help on how I can call these two apis together would be much appreciated.
Instead of the first map operator, try to use flatMap and return from this .flatmap AnyPublisher with your model, that needs to be passed to another request. Pseudocode:
fetchGames -> AnyPublisher<[Game]> // First request
fetchGame(by id: String) -> AnyPublisher<LocationDto> // Second request that depends on model from first one
fetchGames.flatMap { fetchGame(by: $0.id) }

Is it possible to use Observable<Double>.interval in RxSwift?

Hi I have a question here, all the docs I found on RxSwift so far are using observable interval as below:
let subscription = Observable<Int>.interval(0.3, scheduler: scheduler)
.subscribe { event in
print(event)
}
NSThread.sleepForTimeInterval(2)
subscription.dispose()
Now I have the need to implement a Observable<Double>.interval timer, I would like to a Double value in my subscribeNext calls from Observable.
After change above code to Double as a testing, I have an error saying Type Observable<Double> has no member IntegerLiteralType , anyone knows how to implement this in RxSwift?
extension Observable where Element : SignedIntegerType {
/**
Returns an observable sequence that produces a value after each period, using the specified scheduler to run timers and to send out observer messages.
- seealso: [interval operator on reactivex.io](http://reactivex.io/documentation/operators/interval.html)
- parameter period: Period for producing the values in the resulting sequence.
- parameter scheduler: Scheduler to run the timer on.
- returns: An observable sequence that produces a value after each period.
*/
#warn_unused_result(message="http://git.io/rxs.uo")
public static func interval(period: RxTimeInterval, scheduler: SchedulerType)
-> Observable<E> {
return Timer(dueTime: period,
period: period,
scheduler: scheduler
)
}
}
The Element Type is SignedIntegerType. So you can't use Double.If you want to use double , you can rewrite Timer and associated classes.But I don't suggest to rewrite it.You can think another way to do it.
I am not quite sure why you would want a sequence of Double values, because the value just tells you how many times the interval has passed (it does not tell you the amount of time that has passed). So it makes sense that the value is an Int.
But if you really want a sequence of Double values you could easily map the Int sequence to a Double sequence:
_ = Observable<Int>.interval(0.4, scheduler: MainScheduler.instance)
.map { Double($0) }
.subscribeNext { value in
print(value)
}
.addDisposableTo(disposeBag)
Which prints:
1.0
2.0
3.0
4.0
...

RXSwift - takeUntil canceling before next event

Following a similar example to question 39 here: http://reactivex.io/learnrx/
I'm trying to transform a method call search(query: String) into a sequence of those calls.
They way I'm achieving this is by creating a Variable which I update with the query value every time
the search(query: String) method is called.
Then I have this in my init():
_ = queryVariable.asObservable().flatMap({ query -> Observable<[JSON]> in
return self.facebookSearch(query).takeUntil(self.queryVariable.asObservable())
}).subscribeNext({ result in
if let name = result[0]["name"].string {
print(name)
} else {
print("problem")
}
})
If I type "ABC", my search(query: String) method will be called 3 times with "A", "AB", "ABC".
That would be mapped to seq(["A", "AB", "ABC"]) with queryVariable.asObservable().
Then I'm mapping it to Facebook searches (searching people by their names on Facebook).
And with subscribeNext I print the name.
If I don't use the takeUntil, it works as I'd expect, I get 3 sets of results, one for each of my queries("A", "AB", "ABC").
But if I type fast (before Facebook has time to respond to the request), I'd want only one result, for the query "ABC". That's why I added the takeUntil. With it I'd expect the facebookSearch(query: String) call to be ignored when the next query comes in, but it is being canceled for the current query, so with this takeUntil I end up printing nothing.
Is this a known issue or am I doing something wrong?
I used your code and found two solutions to your problem:
1. Use flatMapLatest
You can just use flatMapLatest instead of flatMap and takeUntil. flatMapLatest only returns the results of the latest search request and cancels all older requests that have not returned yet:
_ = queryVariable.asObservable()
.flatMapLatest { query -> Observable<String> in
return self.facebookSearch(query)
}
.subscribeNext {
print($0)
}
2. Use share
To make your approach work you have to share the events of your queryVariable Observable when you also use it for takeUntil:
let queryObservable = queryVariable.asObservable().share()
_ = queryObservable
.flatMap { query -> Observable<String> in
return self.facebookSearch(query).takeUntil(queryObservable)
}
.subscribeNext {
print($0)
}
If you do not share the events, the searchQuery.asObservable() in takeUntil creates its own (duplicate) sequence. Then when a new value is set on the searchQuery Variable it immediately fires a Next event in the takeUntil() sequence and that cancels the facebookSearch results.
When you use share() the sequence in takeUntil is observing the same event as the other sequence and in that case the takeUntil sequence handles the Next event after the facebookSearch has returned a response.
IMHO the first way (flatMapLatest) is the preferred way on how to handle this scenario.

Resources