Reputation: 1311
Suppose that I have a UIButton loginButton
, I want to send a network request while tapping the button with the following code:
override func viewDidLoad() {
super.viewDidLoad()
let session = self.session // NSURLSession
loginButton.rx_tap.subscribeNext { [unowned self] in
session.rx_response(myRequest).subscribe { event in
switch event {
case .Next(let data, let response):
// Handling Response
case .Error(let error):
// Handling Error
default:
return
}
}.addDisposableTo(disposeBag)
}.addDisposableTo(disposeBag)
}
And in such case I can resend the request by tapping the button even if an error occurred with the network request.
Although the code works very well, I thought it is a little bit ugly due to the nested subscription. I tried the flatMap
method to flatten the subscription:
loginButton.rx_tap
.flatMap {
return session.rx_response(myRequest)
}
.subscribe { event in
switch event {
case .Next(let data, let response):
print("Next")
case .Error(let error):
print(error)
default:
return
}
}
.addDisposableTo(disposeBag)
It seems that the two snippets above are of different logic. The latter subscription only works while no error happened just like normal subscription rather than subscribe the network request every time the button has been tapped.
Is there any way to flatten the formal snippet?
Added a snippet of nested subscription:
loginButton.rx_tap
.debug("LoginButtonTapped")
.subscribeNext {
let disposable = session.rx_response(myRequest)
.debug("AuthorizationRequest")
.subscribe(
onNext: { [unowned self] data, response in
// Handling Response
},
onError: { [unowned self] error in
// Showing Error
})
disposable.addDisposableTo(self.disposeBag)
let alert = UIAlertController(title: "Please Wait.", message: "Requesting Tokens", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
disposable.dispose()
alert.dismissViewControllerAnimated(true, completion: nil)
})
self.presentViewController(alert, animated: true, completion: nil)
}.addDisposableTo(disposeBag)
Error can be caught using the following code:
let disposable = loginButton.rx_tap
.map { session.rx_response(request) }
.flatMap { [unowned self] in $0.catchError(self.presentError) }
.subscribeNext { data, response in
// Handling Response
}
I also need to cancel the network request if necessary. If I manually dispose the disposable
in the above snippet, the subscription will be disposed and I can not send the request again.
Upvotes: 1
Views: 2513
Reputation: 7045
You have 2 ways to achieve the requested behaviour. The first one is to user map
and then switchLatest
, this is the classic way. The second one is to user flatMap
if you need to catch
/retry
in the network request sequence.
Ash Furrow has a very nice example in a workshop demo code using the second example and Moya:
submitButton.rx_tap.map { _ -> Observable<MoyaResponse> in
return provider.request(.Image)
}.flatMap() { obs in
return obs.filterSuccessfulStatusCodes()
.mapImage()
.catchError(self.presentError)
.filter({ (thing) -> Bool in
return thing != nil
})
}
.take(1)
.bindTo(imageView.rx_image)
.addDisposableTo(disposeBag)
Alternatively, you can check the Github example in the RxSwift repository to see how these sort of things are handled.
Upvotes: 2