Bartłomiej Semańczyk
Bartłomiej Semańczyk

Reputation: 61880

How to perform subscription within subscription using RxSwift?

Currently I have the following working code:

    mainView.saveButton.rx.tap.bind { [weak self] in
        if let self = self {
            // start indicator
            self.viewModel.save() // Completable
                .subscribe(onCompleted: { [weak self] in
                    // completed
                }, onError: { error in
                    // error
                })
                .disposed(by: self.disposeBag)
        }
    }.disposed(by: disposeBag)

But I know it is not a good approach (due to nested subscriptions), so I am trying to create working equivalent (now with no success):

    mainView.saveButton.rx.tap
        .do(onNext: { [weak self] in
            // start indicator
        })
        .flatMapFirst { _ in
            self.viewModel.save() // Completable
        }
        .subscribe(onError: { error in
            // error
        }, onCompleted: { [weak self] in
            // completed
        })
        .disposed(by: disposeBag)

Subscribe closure is not calling at all. Why?

Upvotes: 1

Views: 1009

Answers (1)

Daniel T.
Daniel T.

Reputation: 33979

The flatMap won't complete until its source (the button tap) completes. It can't because it has to be ready if the user taps the button again.

The most common way to fix this would be to have your save() function return a Single<Void> instead of a Completable. If you don't want to (or can't) do that, then you can use andThen to send an event on completion.

Also, you don't want an error to escape the flatMap because that will break the button tap. This means you need to catch any errors and convert them to next events within the closure. You can do that with materialize() or catch and use a dedicated subject. (Check out my ErrorRouter for example.)

Something like this:

let errorRouter = ErrorRouter()
let saveSuccessful = mainView.saveButton.rx.tap
    .flatMap { [viewModel] in
        viewModel.save()
            .andThen(Observable.just(()))
            .rerouteError(errorRouter)
    }

Observable.merge(
    mainView.saveButton.rx.tap.map(to: true),
    saveSuccessful.map(to: false)
)
.bind(onNext: { isIndicatorVisible in
    // handle indicator
})
.disposed(by: disposeBag)

errorRouter.error
    .bind(onNext: { error in
        // handle error
    })
    .disposed(by: disposeBag)

Upvotes: 0

Related Questions