1
votes

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

2
I think ZIP is the thing you are looking for: reactivex.io/documentation/operators/zip.html - ULazdins

2 Answers

6
votes

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)
0
votes

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.