How to map RxSwift Observable and Result
10,428
Solution 1
Updated:
It's a detailed example, hope this may help:
enum RequestError: Error {
case unknown
}
func requestToken() -> Observable<String> {
return Observable.create { observer in
let success = true
if success {
observer.onNext("MyTokenValue")
observer.onCompleted()
} else {
observer.onError(RequestError.unknown)
}
return Disposables.create()
}
}
func requestData(token: String) -> Observable<[String: Any]> {
return Observable<[String: Any]>.create { observer in
let success = false
if success {
observer.onNext(["uid": 007])
observer.onCompleted()
} else {
observer.onError(RequestError.unknown)
}
return Disposables.create()
}
.map { (data: [String: Any]) in
var newData = data
newData["token"] = token
return newData
}
}
requestToken() // () -> Observable<String>
.flatMapLatest(requestData) // Observable<String> -> Observable<[String: Any]>
.materialize() // Observable<[String: Any]> -> Observable<Event<[String: Any]>>
.subscribe(onNext: { event in
switch event {
case .next(let dictionary):
print("onNext:", dictionary)
case .error(let error as RequestError):
print("onRequestError:", error)
case .error(let error):
print("onOtherError:", error)
case .completed:
print("onCompleted")
}
})
.disposed(by: disposeBag)
Original:
I think it's much easier to achieve it using materialize()
with less extra work:
func requestToken() -> Observable<String> { return .empty() }
func requestData(token: String) -> Observable<NSDictionary> { return .empty() }
enum RequestError: Error {}
requestToken()
.flatMapLatest(requestData)
.materialize()
.subscribe(onNext: { event in
switch event {
case .next(let dictionary):
print("onNext:", dictionary)
case .error(let error as RequestError):
print("onRequestError:", error)
case .error(let error):
print("onOtherError:", error)
case .completed:
print("onCompleted")
}
})
.disposed(by: disposeBag)
Hope this may help.
Solution 2
If you use the built in error system, you can save yourself from having to manually pass the error along and all the switches that would entail. You can cast the error at the end.
I would do something more like this:
// this is necessary to handle adding the token to the dictionary.
extension Dictionary {
/// An immutable version of update. Returns a new dictionary containing self's values and the key/value passed in.
func updatedValue(_ value: Value, forKey key: Key) -> Dictionary<Key, Value> {
var result = self
result[key] = value
return result
}
}
// function signatures, note that they don't return Results anymore.
func requestToken() -> Observable<String> { /*...*/ }
func requestData(withToken: String) -> Observable<[String: Any]> { /*...*/ }
requestToken().flatMapLatest {
requestData(token: $0)
.map { $0.updatedValue($0, forKey: "token") }
.map { .success($0) }
}.catchError {
Observable.just(.failure($0 as! RequestError))
}
With the above, the end result would be an Observable<Result<[String: Any], RequestError>>
just like in your case, but the error handling is much cleaner.
If you can't change the signatures of the two functions you are using then I would do this:
func throwError<T, U: Error>(result: Result<T, U>) throws -> T {
switch result {
case .success(let token):
return token
case .failure(let error):
throw error
}
}
requestToken().map {
try throwError(result: $0)
}.flatMapLatest {
requestData(token: $0)
.map { try throwError(result: $0) }
.map { $0.updatedValue($0, forKey: "token") }
}
.map { .success($0) }
.catchError {
Observable.just(.failure($0 as! RequestError))
}
Related videos on Youtube
Author by
Rodrigo Ruiz
Updated on June 04, 2022Comments
-
Rodrigo Ruiz almost 2 years
I have a quick question:
- I have a network request that returns
Observable<Result<String, RequestError>>
, let’s call itrequestToken
- if this request succeeds, I want to use the
String
(token) to do another request that returnsObservable<Result<NSDictionary, RequestError>>
, let’s call itrequestData
- when that second request comes back, I wanna merge the token into its dictionary
- in the end I wanna map from
Observable<Result<String, RequestError>>
toObservable<Result<NSDictionary, RequestError>>
How can I achieve that without multiple nested levels in my code?
This is what I have today:
requestToken() .flatMap({ result -> Observable<Result<NSDictionary, RequestError>> in switch result { case .success(let token): return requestData(token: token).map({ $0.map({ $0 + ["token": token] }) }) case .failure(let error): return Observable.of(.failure(error)) } })
- I have a network request that returns
-
Rodrigo Ruiz almost 7 yearsI couldn't find
materialize()
on my Observable. Is it present on the 3.0 version? -
beeth0ven almost 7 yearsYes it's available since version 3.4.0 github.com/ReactiveX/RxSwift/releases/tag/3.4.0
-
Rodrigo Ruiz almost 7 yearsI'm not sure I understand what
materialize
is for, first, myrequestToken
returns aResult
, not aString
, so I can't.flatMapLatest(requestData)
. Second, I need the token merged with the resulting dictionary ofrequestData
. Could you clarify? -
beeth0ven almost 7 yearsYes, you see
requestToken
returns aResult
, It's difficult toflatMap
with aResult
. So my code shows you need to changerequestToken
to returnObservable<String>
instead ofObservable<Result<String>>
. So my code's flow is() -> Observable<String> -> Observable<NSDictionary> -> Observable<Event<NSDictionary>>
. AndEvent
is something likeResult
which has three casenext
,error
,completed
. Whatmaterialize()
dose is changeObservable<NSDictionary>
toObservable<Event<NSDictionary>>
. So you can subscribe to the it. -
Rodrigo Ruiz almost 7 yearsBut then how will I know if
requestToken
can fail if it's saying it always returns a String?