I've been working on a search controller that is using RxSwift to update DataSource when user types in Search field, like it's described here: http://www.thedroidsonroids.com/blog/ios/rxswift-examples-3-networking/
That is my viewmodel:
struct SearchControllerViewModel {
let provider: RxMoyaProvider<ThreadifyEndpoint>
let startsWith: Observable<String>
func startSearching() -> Observable<[SearchedNodeViewModel]> {
return startsWith
.observeOn(MainScheduler.instance)
.flatMapLatest { query -> Observable<[SearchedNodeViewModel]?> in
return self.findNodes(query)
}.replaceNilWith([])
}
internal func findNodes(startsWith: String) -> Observable<[SearchedNodeViewModel]?> {
return self.provider
.request(ThreadifyEndpoint.SearchForNodes(startsWith: startsWith))
.mapArrayOptional(SearchedNodeViewModel.self)
}
}
Now I want new data to be loaded not only when user is typing but either when sh's scrolling down.
I was thinking to use combineLatest to observe both rx_text and rx_offset but I can't pass Observable to combineLatest because of compilation error.
The compilation error you're seeing is due to you not using an actual method. The method signature you're intending to use is:
public class func combineLatest<O1 : ObservableType, O2 : ObservableType>(source1: O1, _ source2: O2, resultSelector: (O1.E, O2.E) throws -> Element) -> RxSwift.Observable<Element>
Notice that there's a third argument that you're forgetting: resultSelector. That's supposed to be a block that describes how you want to combine the latest elements into a new element.
Based on your error message, I'm thinking you're using it like this:
let combined = Observable.combineLatest(stringObservable, pointObservable)
Whereas, you should be using it like this:
let combined = Observable.combineLatest(stringObservable, pointObservable) { (s, p) in
return "\(s), \(p)" // or construct your new element however you'd like
}
Without the block, RxSwift doesn't know how you'd like to combine them. You might have been thinking it would just default to making a new tuple of (String, CGPoint) as the element, but it makes no such assumptions and requires you to tell it.
Related
The question is quite complicated but in code seems to look simpler (I hope)
I have a functions like bellow
fun trackFoos(): Observable<List<Foo>> {/*....*/}
fun getBarForFoo(fooState: Foo.State): Single<Bar>
And I was having some code which is bound to other parts of apps so I cannot change it too much.
Observable.combineLatest(
trackFoos(),
Observable.interval(1, TimeUnit.SECONDS),
BiFunction() {foos: List<Foo>, _:Long -> foos}
).subscribe { foos -> /*....*/ }
And now I need to pass also object of Bar for each foo object using this getBarForFoo
So I need to write trackPairOfFooAndBar() using trackFoos() and getBarForFoo() to use it as below:
Observable.combineLatest(
trackPairOfFooAndBar(),
Observable.interval(1, TimeUnit.SECONDS),
BiFunction() {fooBarList: List<Pair<Foo,Bar>>, _:Long -> fooBarList}
).subscribe { fooBarList-> /*....*/ }
I saw this answer: https://stackoverflow.com/a/47676208/1461568 but there is assumption that second call is observable (I have Single)
So how to do it?
You can do it like this:
Transform trackFoos() from Observable<List<Foo>> to Observable<Foo>
For each Foo do getBarForFoo() and map result so it really returns Pair<Foo, Bar>
(Optional) If you need Single<List<Pair<Foo,Bar>>> you can just call toList()
(Optional) If you really need Observable then you can just call toObservable()
In code:
fun trackPairOfFooAndBar() : Observable<Pair<Foo, Bar>> {
return trackFoos()
.flatMapIterable { it } // or flatMap {list -> Observable.fromIterable(list)}
.flatMapSingle { foo -> getBarForFoo(foo.state).map { foo to it } }
// optional, depending what is the final signature that you want
//.toList().toObservable
}
I've been running into this every now and then I'm always questioning myself whether I'm using RxSwift (or reactive means altogether) the wrong way.
The challenge is converting value types to something representable on the UI.
Usually on the lower level I'm storing data with simple types but I need something more stateful on the UI level. Just to give you an example, consider I have list of following types:
struct Person {
let firstName: String
let lastName: String
}
On the UI, however, I'm binding view models created from these items into a UITableView instance. I could achieve this by simply mapping from one type to another:
let displayedPersons = listOfPersons.map { PersonViewModel($0) }
This would make all items to be recreated on each update which I'm trying to avoid. I'm using MVVM and would like to keep the view model instances due to their transient state. Reloading table view on each update would also mess up animations.
I'm thinking if a custom binding could help here, binding one observable to another with cached mapping. Another solution that I've ended up doing is simply looping the observable so that when mapping, I get the previous value which I'll use as a cache.
Effectively I would need to map only the new items and keep the existing ones. Any ideas how would I achieve this?
In my very biased opinion, MVVM is good only for very complex UI where elements need to update dynamically and independently from each other. For all other cases I use my own library https://github.com/maxvol/RaspSwift (NB: it is not only for UI, but for UI as well). The core idea stems from MVI, and boils down to having a new snapshot of the state on every mutating event. So in your case the state would contain a collection of cached PersonViewModel, which will be partially updated upon receiving mutating events. The whole thing would be bound to UITableView via RxDataSources library.
One simple solution that I'm currently using is simply to map and recycle the old items if they exist.
I've created an extension to make it work with sequences. In memoryLookup you will receive the previous values and can reuse any item from the previous round.
public extension ObservableType where E: Sequence {
public func mapWithMemory<R>(memoryLookup: #escaping (Self.E.Element, [R]) throws -> R?, transform: #escaping (Self.E.Element) throws -> R) -> RxSwift.Observable<[R]> {
return self.scan([]) { (acc, elements) -> [R] in
let mapped = try elements.map { e in
return try memoryLookup(e, acc) ?? transform(e)
}
return mapped
}
}
}
And here's an example usage where an array of Ints is mapped to array of Strings.
func testMapWithMemory() {
var creationCounts = [Int: Int]()
let items = Observable.from([[1, 2],[2, 3],[2, 3]])
let mapped = items.mapWithMemory(memoryLookup: { (item, previousItems) -> String? in
return previousItems.first { $0 == "\(item)" }
}) { (item) -> String in
creationCounts[item, default: 0] += 1
return "\(item)"
}
let xs = try! mapped.toBlocking().toArray().last!
XCTAssertEqual(xs, ["2", "3"])
XCTAssertEqual(creationCounts, [
1: 1,
2: 1,
3: 1
])
}
Use at your own risk. And feel free to improve and share.
Also note that this is only useful if you need to avoid creating new items. In my case I'm using classes and binding UI elements to these items, so I don't want to recreate those.
I made a few gateways / providers to integrate with an API in my API, using RX Swift and I'm trying to handle the pagination in what seems to me like a clean and simple way.
Basically, the function signature would look like that:
func getPlaces(with location: CLLocationCoordinate2D) -> Observable<(value: [Place], next: Observable<(value: [Places], Observable<(value: [Place], next: ... ... >>
This quickly appears impractical, so I tried creating a typealias for that:
typealias Result = Observable<(value: [Place], next: Result?)>
So my function signature would look like this:
func getPlaces(with location: CLLocationCoordinate2D) -> Result
But Xcode wouldn't get fooled so easily and calls me out for referencing my typealias inside itself
So... is it even doable ? How ?
I don't think this is possible using a typealias because you are creating an infinite type. The only way I can think of is to make Observable a recursive enumeration:
enum Observable {
case end([Place])
indirect case node([Place], Observable)
}
So, I mixed my approach with Nibr's, using a single case. This allows to handle pagination much more simply (in my opinion) from the ViewModel's side
enum Result<T> {
indirect case node([T], Observable<Result>?)
var value: [T] {
if case let GatewayResult.node(value, _) = self {
return value
}
return []
}
var next: Observable<Result>? {
if case let GatewayResult.node(_, next) = self {
return next
}
return nil
}
}
My app has a status area at the top that shows progress information (similar to Xcode and iTunes). I want to update it by injecting side effects into an event stream, using a closure that converts the stream's value into the ProgressUpdate value. I'm using an extension on SignalProducer so any signal producer in my app can update the app's status area (there is a lot more involved to allow for multiple signals at once, but that doesn't affect this problem).
I'm basing it on SignalProducer's on(starting:, started:, ...). It requires the latest swift 3.1 beta to allow the constraint on the error type, but this is straight from a playground.
import ReactiveSwift
struct Rc2Error: Error {
}
struct ProgressUpdate {
let message: String
let value: Double = -1
}
class MacAppStatus {
fileprivate func process(event: (Event<ProgressUpdate, Rc2Error>) -> Void)
{
//update UI based on the event
}
}
extension SignalProducer where Error == Rc2Error {
func updateProgress<Value>(status: MacAppStatus, converter: #escaping (Value) -> ProgressUpdate) -> SignalProducer<Value, Error>
{
return SignalProducer<Value, Error> { observer, compositeDisposable in
self.startWithSignal { signal, disposable in
compositeDisposable += disposable
compositeDisposable += signal
.on(event: { (orignal) in
switch original {
case .completed:
status.process(Event<ProgressUpdate, Rc2Error>.completed)
case .interrupted:
status.process(Event<ProgressUpdate, Rc2Error>.interrupted)
case .failed(let err):
status.process(Event<ProgressUpdate, Rc2Error>.failed(err))
case .value(let val):
status.process(Event<ProgressUpdate, Rc2Error>.value(converter(val)))
}
})
.observe(observer)
}
}
}
}
```
The last line of .observe(observer) produces an error:
error: cannot convert value of type 'Observer<Value, Rc2Error>' to expected argument type 'Observer<_, Rc2Error>'
Any ideas why this conversion fails? Suggestions on a different way to accomplish this?
It looks like it was just bad error reporting from the compiler. The actual problem was that process() should take an Event, not a closure that takes an event. It also needed an empty external parameter name.
Changing the signature to
fileprivate func process(_ event: Event<ProgressUpdate, Rc2Error>)
and fixing the original typo Mike Taverne pointed out fixed it.
I have some doubts in my approach. I have two type of Observables:
//I can fetch from the server the houses and save them in the database
func houses() -> Observable<[House]>
//Given a houseId, I can fetch it's details and save them in the database
func houseDetail(id: Int) -> Observable<Family>
I would like to do an Observable which first fetch all houses, and then fetch the families. What I did is something like that:
//I am an observable which, when I complete, all data are saved
func fetchAllHousesAndFamily() -> Observable<Any> {
var allHousesObservables: [Observable] = []
for house in houses {
allHousesObservables.append(houseDetail(house.id))
}
return Observable.combineLatest(allHousesObservables)
}
But this for...it doesn't seem to be reactive style to me, and it seems like a hack because I don't know enough about rx operators.
Do you have the right way to do it in the rxworld ?
Thank you
To get all family from the result of houses, you will want to use the flatMap operator. flatMap accepts a closure with signature T -> Observable<U>, so, in our specific example, it will be a function of type [House] -> Observable<Family>.
houses().flatMap { houses in
let details: [Observable<Family>] = houses.map { houseDetail($0.id) } // [1]
return Observable.from(details).merge() // [2]
}
[1]: We are mapping an array of house to an array of Observable<Family>.
[2]: from(_:) converts [Observable<Family>] to Observable<Observable<Family>>. merge() then transform it to Observable<Family>
We now have an observable that will emit one next event for each house with its family details.
If we'd prefer to keep the House value around, we could simply map again on the first line, like so:
let details: [Observable<(House, Family)>] = house.map { house in
houseDetail(house.id).map { (house, $0) }
}