Create Multiple Observables from a Single one - RxSwift - ios

I have relatively expensive operation so I am willing to perform that operation once and create 2 Observables from it.
Here is how it looks:
let outputObservable1: Observable<Bool>
let outputObservable2: Observable<Bool>
(outputObservable1, outputObservable2) = inputObservable1.zip(inputObservable2).map { booleanCondition1, booleanCondition2 in
// different condition combinations create different outputObservables
}
I am guessing map is not the right operator here as it will only yield one observable. How can I mix and match the conditions and return 2 Observables at once?

Based on my understanding, you just need to use map
let inputs = Observable.zip(inputObservable1, inputObservable2)
.share() // you only need this if one of your inputs is making a network request.
let outputObservable1 = inputs
.map { first, second in
return true // or false depending on the values of first & second.
}
let outputObservable2 = inputs
.map { first, second in
return true // or false depending on the values of first & second.
}

Related

How do I observe two rx sequences and subscribe with two closure arguments?

I want to observe two behaviorRelays with a single observer, wait for both relays to emitt their values, then in the subscription have two seperate closure arguemts, one for each relay. Something like this:
let one = firmwareService.basicIODeviceUnit.compactMap { $0?.canBeUpdated }
let two = firmwareService.motorDeviceUnit.compactMap { $0?.canBeUpdated }
Observable.of(one, two).flatMap{ $0 }.subscribe(onNext: { a, b in
print("--", a, b)
}).disposed(by: disposeBag)
The above code isn't allowed. The operators like merge or zip seem to bundle both relays into a single closure argumet so I guess they won't work. What do I use?
I have looked through this thread, so it should be possible, but I can't wrap my head around it since I use swift
RxJS Subscribe with two arguments
I'm not sure what you mean because zip does exactly what you want. So does combineLatest.
let one = firmwareService.basicIODeviceUnit.compactMap { $0?.canBeUpdated }
let two = firmwareService.motorDeviceUnit.compactMap { $0?.canBeUpdated }
Observable.zip(one, two)
.subscribe(onNext: { a, b in
print("--", a, b)
})
.disposed(by: disposeBag)
you can use combineLatest:
combineLatest is an operator which you want to use when value depends on the mix of some others Observables.
When an item is emitted by either of two Observables, combine the latest item emitted by each Observable via a specified closure and emit items based on the results of this closure.
For reference follow this -
combineLatest meduim link

Chaining RxSwift observable with different type

I need request different types of models from network and then combine them into one model.
How is it possible to chain multiple observables and return another observable?
I have something like:
func fetchDevices() -> Observable<DataResponse<[DeviceModel]>>
func fetchRooms() -> Observable<DataResponse<[RoomModel]>>
func fetchSections() -> Observable<DataResponse<[SectionModel]>>
and I need to do something like:
func fetchAll() -> Observable<(AllModels, Error)> {
fetchSections()
// Then if sections is ok I need to fetch rooms
fetchRooms()
// Then - fetch devices
fetchDevices()
// And if everything is ok create AllModels class and return it
// Or return error if any request fails
return AllModels(sections: sections, rooms: rooms, devices:devices)
}
How to achieve it with RxSwift? I read docs and examples but understand how to chain observables with same type
Try combineLatest operator. You can combine multiple observables:
let data = Observable.combineLatest(fetchDevices, fetchRooms, fetchSections)
{ devices, rooms, sections in
return AllModels(sections: sections, rooms: rooms, devices:devices)
}
.distinctUntilChanged()
.shareReplay(1)
And then, you subscribe to it:
data.subscribe(onNext: {models in
// do something with your AllModels object
})
.disposed(by: bag)
I think the methods that fetching models should reside in ViewModel, and an event should be waiting for start calling them altogether, or they won't start running.
Assume that there's a button calls your three methods, and one more button that will be enabled if the function call is succeeded.
Consider an ViewModel inside your ViewController.
let viewModel = ViewModel()
In ViewModel, declare your abstracted I/O event like this,
struct Input {
buttonTap: Driver<Void>
}
struct Output {
canProcessNext: Driver<Bool>
}
Then you can clearly transform your Input into Output by making function like this in ViewModel.
func transform(input: Input) -> Output {
// TODO: transform your button tap event into fetch result.
}
At viewDidLoad,
let output = viewModel.transform(input: yourButton.rx.tap.asDriver())
output.drive(nextButton.rx.isEnabled).disposed(by: disposeBag)
Now everything's ready but combining your three methods - put them in ViewModel.
func fetchDevices() -> Observable<DataResponse<[DeviceModel]>>
func fetchRooms() -> Observable<DataResponse<[RoomModel]>>
func fetchSections() -> Observable<DataResponse<[SectionModel]>>
Let's finish the 'TODO'
let result = input.buttonTap.withLatestFrom(
Observable.combineLatest(fetchDevices(), fetchRooms(), fetchSections()) { devices, rooms, sections in
// do your job with response data and refine final result to continue
return result
}.asDriver(onErrorJustReturn: true))
return Output(canProcessNext: result)
I'm not only writing about just make it work, but also considering whole design for your application. Putting everything inside ViewController is not a way to go, especially using Rx design. I think it's a good choice to dividing VC & ViewModel login for future maintenance. Take a look for this sample, I think it might help you.

Changing filter on Realm notification

I wonder if it is possible to use the same notificationblock on a resultset but change the filter? For example: I have two queries, one with isDelivered = true and one where isDelivered = false. I would like to have one Resultset with different filters and then switch the resultset depending on if I want to see delivered or undelivered items. Is that possible or do I need to create two notificationblock for this?
It's not possible to retroactively change the predicate query used to create a Realm Results object. But it is possible to attach the same notification block to two separate Results instances in order to share the handling logic.
let notificationBlock: ((RealmCollectionChange) -> Void) = { changes in
// Perform common update logic in here
}
let deliveredObjects = realm.objects(MyObject.self).filter("isDelivered = true")
let delieveredNotificationToken = deliveredObjects.addNotificationBlock(notificationBlock)
let undeliveredObjects = realm.objects(MyObject.self).filter("isDelivered = false")
let undelieveredNotificationToken = undeliveredObjects.addNotificationBlock(notificationBlock)

RxSwift. CombineLatest. Not all observables emitted

Observable.combineLatest(...){...} contains several observables, but some of these observables were not emitted.
combineLatest emits only when all observables in this method were emitted.
How to skip not emitted observables and emit combineLatest?
let tap = firstButton.rx.tap.asObservable().map{ (_) -> Observable<Item> ...}
let textfieldObservable = viewTextField.rx.text.orEmpty.asObservable()
submitButton.rx.tap.withLatestFrom(Observable.combineLatest(textfieldObservable, tap ... )).flatMapLatest({
...
// this method will not be executed without tap on firstButton before tapping on submitButton
}
)
combineLatest uses a closure that takes in as many arguments as it combines observables. So it makes sense it will wait for all the observables it combines to provide a value before it calls its closure.
But if you can find a sain default values for each of the observables provided to combineLatest, you could use startWith(_:) to force them into having an initial value.
This is what the code would look like using nil for item and the empty string for text
let tapObservable: Observable<Item> = // ...
let textField: Observable<String> = // ...
let combined = Observable.combineLatest(
tapObservable.map { /* map everything to optional */ Optional.some($0) }.startWith(nil),
textField.startWith("")
) { item, text in
// combine item and text
}
submitButton.rx.tap.withLatestFrom(combined)

What is a less verbose way to repeatedly pop() items from a vector?

When using a vector as a stack (storing states which are pushed and popped).
while stack.len() != 0 {
let state = stack.pop().unwrap();
// ... optionally push other states onto the stack.
}
Is there a less verbose way to do this in Rust?
You can use the fact that pop() returns an Option<T> and match on that using a while let loop:
while let Some(state) = stack.pop() {
// ... fine to call stack.push() here
}
The while let desugars to something like the following:
loop {
match stack.pop() {
Some(state) => {
// ... fine to call stack.push() here
}
_ => break
}
}
Just to offer an alternative approach, you can also use the drain method to remove elements and give them to you in an Iterator.
stack.drain(..).map(|element| ...and so on
or
for element in stack.drain(..) {
//do stuff
}
You can also provide a RangeArgument if you only want to remove a certain range of elements. This can be provided in the form of <start-inclusive>..<end-exclusive>. Both the start and end of the range argument are optional and just default to the start of end of the vector, so calling drain(..) just drains the entire vector, while drain(2..) would leave the first 2 elements in place and just drain the rest.

Resources