I have this code:
...
let minMaxReq = networkProvider.getMinMaxAmortization(id: id)
let emitExtractReq = networkProvider.getEmitExtract(id: id)
self.isLoading.accept(true)
Observable.zip(minMaxReq.asObservable(), emitExtractReq.asObservable()) { (minMaxResp, emitExtractResp) in
return (minMaxResp, emitExtractResp)
}.subscribe(onNext: { [weak self] responses in
let minMaxResp = responses.0
let emitExtractResp = responses.1
guard let self = self else { return }
self.isLoading.accept(false)
self.getMinMaxAmortizationResponse.accept(minMaxResp)
self.receiptsCNH.accept(emitExtractResp)
}, onError: { [weak self] error in
self?.isLoading.accept(false)
self?.receivedError.accept(error)
}).disposed(by: disposeBag)
In this case all errors from both requests will end up in the onError closure, how can I handle the error from minMaxReq in a different onError closure?
My goal is to make the 2 requests at the same time but handle their error with different closures. How can I achieve this?
thanks
Have you tried the materialize operator? Maybe it can be useful for the implementation you need. Annex an example of how it is usually used:
let valid = network.getToken(apikey)
.flatMap{ token in self.verifyToken(token).materialize()}
.share()
valid
.compactMap { $0.element }
.subscribe(onNext: { data in print("token is valid:", data) })
.disposed(by: disposeBag)
valid
.compactMap { $0.error?.localizedDescription }
.subscribe(onNext: { data in print("token is not valid:", data) })
.disposed(by: disposeBag)
In that way, you could divide the stream into two and give it the appropriate treatment.
Another option might be to manipulate the error event in the minMaxReq operation. Something similar to:
let minMaxReq = networkProvider.getMinMaxAmortization(id: id)
.catchError({ error in Observable.empty()
.do(onCompleted: { /* Do anything with no side effect*/ }) })
let emitExtractReq = networkProvider.getEmitExtract(id: id)
Observable.zip(...)
Here is an article that explains more detail Handling Errors in RxSwift
I found this solution helpful for me RxSwift zip operator when one observable can fail
Need to catch error before add to zip
let request1 = usecase.request1().asObservable()
let request2 = usecase.request2().catchErrorJustReturn([]).asObservable() // fetching some not so important data which are just good to have.Return empty array on error
Observable.zip(request1,request2)..observeOn(MainScheduler.instance)
.subscribe(
onNext: { //success code },
onError: { _ in //error code}).disposeBy(bag:myDisposeBag)
But I changed a bit the final:
let request2 = usecase.request2().catchError { _ in
return .just([])
}
Related
I am completely new to RxSwift. I managed to load my table view but now I would like to make a call every 10 seconds.
I was reading here that I should probably use Observable<Int>.interval(10, scheduler: MainScheduler.instance), I tried without much success.
class MarketService: MarketServiceProtocol {
func fetchMarkets() -> Observable <[Market]> {
return Observable.create { observer -> Disposable in
RestManager.shared.makeRequest(withEndPoint: "market/v2/get-summary?region=US" , withHttpMethod: .get) { result in
if let error = result.error {
observer.onError(error)
return
}
guard let response = result.response,
200 ... 299 ~= response.httpStatusCode else {
return
}
guard let data = result.data else {
return
}
do {
let decodedData = try JSONDecoder().decode(MarketResult.self, from: data)
observer.onNext(decodedData.marketSummaryAndSparkResponse.markets)
} catch {
observer.onError(error)
}
}
return Disposables.create { }
}
}
}
then I call in my view controller:
viewModel.fetchMarketViewModels().observe(on: MainScheduler.instance).bind(to: tableView.rx.items(cellIdentifier: HomeTableViewCell.cellIdentifier)) {
index, viewModel, cell in
guard let cell = cell as? HomeTableViewCell else { return }
cell.setupData(viewModel: viewModel)
}.disposed(by: self.disposableBag)
There are a couple of problems with your Observable.create closure. You have to make sure that something is sent to the observer in every path, otherwise the Observable will call the function and then not emit anything and you will not know why.
Also, you want to minimize the amount of logic being performed in the closure passed to create. You are doing way too much in there.
So let's simplify the code in the create closure as much as possible first:
extension RestManager {
func rx_makeRequest(withEndPoint endPoint: String, withHttpMethod method: HttpMethod) -> Observable<(response: MyHTTPURLResponse, data: Data)> {
Observable.create { observer in
self.makeRequest(withEndPoint: endPoint, withHttpMethod: method) { result in
if let response = result.response, let data = result.data {
observer.onNext((response, data))
observer.onCompleted()
}
else {
observer.onError(result.error ?? RxError.unknown)
}
}
return Disposables.create() // is there some way of canceling a request? If so, it should be done here.
}
}
}
This does the bare minimum. Just wraps the underlying callback and nothing else. Now your fetchMarkets call is much simpler:
class MarketService: MarketServiceProtocol {
func fetchMarkets() -> Observable <[Market]> {
return RestManager.shared.rx_makeRequest(withEndPoint: "market/v2/get-summary?region=US", withHttpMethod: .get)
.do(onNext: { result in
guard 200...299 ~= result.response.httpStatusCode
else { throw URLError.httpRequestFailed(response: result.response, data: result.data) }
})
.map { try JSONDecoder().decode(MarketResult.self, from: $0.data).marketSummaryAndSparkResponse.markets }
}
}
Now to the meat of your question. How to make the network call every 10 seconds... Just wrap your network call in a flatMap like this:
Observable<Int>.interval(.seconds(10), scheduler: MainScheduler.instance)
.flatMapLatest { _ in
viewModel.fetchMarketViewModels()
}
.observe(on: MainScheduler.instance)
.bind(to: tableView.rx.items(cellIdentifier: HomeTableViewCell.cellIdentifier)) { index, viewModel, cell in
guard let cell = cell as? HomeTableViewCell else { return }
cell.setupData(viewModel: viewModel)
}
.disposed(by: self.disposableBag)
Learn more about flatMap and its variants from this article.
I have a BehaviorSubject where my tableview is bound to through RxDataSources.
Besides that, I have a pull to refresh which creates an observable that updates the data and updates the data in the BehaviorSubject so that my UITableView updates correctly.
Now the question is, how do I handle the error handling for whenever my API call fails?
Few options that I have thought of was:
Subscribe to the observer's onError and call the onError of my BehaviorSubject\
Somehow try to concat? or bind(to: ..)
Let another subscriber in my ViewController subscribe besides that my tableview subscribes to the BehaviorSubject.
Any suggestions?
Ideally, you wouldn't use the BehaviorSubject at all. From the Intro to Rx book:
The usage of subjects should largely remain in the realms of samples and testing. Subjects are a great way to get started with Rx. They reduce the learning curve for new developers, however they pose several concerns...
Better would be to do something like this in your viewDidLoad (or a function that is called from your viewDidLoad):
let earthquakeData = Observable.merge(
tableView.refreshControl!.rx.controlEvent(.valueChanged).asObservable(),
rx.methodInvoked(#selector(UIViewController.viewDidAppear(_:))).map { _ in }
)
.map { earthquakeSummary /* generate URLRequest */ }
.flatMapLatest { request in
URLSession.shared.rx.data(request: request)
.materialize()
}
.share(replay: 1)
earthquakeData
.compactMap { $0.element }
.map { Earthquake.earthquakes(from: $0) }
.map { $0.map { EarthquakeCellDisplay(earthquake: $0) } }
.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: EarthquakeTableViewCell.self)) { _, element, cell in
cell.placeLabel.text = element.place
cell.dateLabel.text = element.date
cell.magnitudeLabel.text = element.magnitude
cell.magnitudeImageView.image = element.imageName.isEmpty ? UIImage() : UIImage(named: element.imageName)
}
.disposed(by: disposeBag)
earthquakeData
.compactMap { $0.error }
.map { (title: "Error", message: $0.localizedDescription) }
.bind { [weak self] title, message in
self?.presentAlert(title: title, message: message, animated: true)
}
.disposed(by: disposeBag)
The materialize() operator turns a Event.error(Error) result into an Event.next(.error(Error)) so that the chain won't be broken down. The .compactMap { $0.element } emits only the successful results while the .compactMap { $0.error } emits only the errors.
The above code is adapted from my RxEarthquake sample.
I'm using Combine and it happens to me many times that I have the need to emit Publishers with single values.
For example when I use flat map and I have to return a Publisher with a single value as an error or a single object I use this code, and it works very well:
return AnyPublisher<Data, StoreError>.init(
Result<Data, StoreError>.Publisher(.cantDownloadProfileImage)
)
This creates an AnyPublisher of type <Data, StoreError> and emits an error, in this case: .cantDownloadProfileImage
Here a full example how may usages of this chunk of code.
func downloadUserProfilePhoto(user: User) -> AnyPublisher<UIImage?, StoreError> {
guard let urlString = user.imageURL,
let url = URL(string: urlString)
else {
return AnyPublisher<UIImage?, StoreError>
.init(Result<UIImage?, StoreError>
.Publisher(nil))
}
return NetworkService.getData(url: url)
.catch({ (_) -> AnyPublisher<Data, StoreError> in
return AnyPublisher<Data, StoreError>
.init(Result<Data, StoreError>
.Publisher(.cantDownloadProfileImage))
})
.flatMap { data -> AnyPublisher<UIImage?, StoreError> in
guard let image = UIImage(data: data) else {
return AnyPublisher<UIImage?, StoreError>
.init(Result<UIImage?, StoreError>.Publisher(.cantDownloadProfileImage))
}
return AnyPublisher<UIImage?, StoreError>
.init(Result<UIImage?, StoreError>.Publisher(image))
}
.eraseToAnyPublisher()
}
Is there an easier and shorter way to create an AnyPublisher with a single value inside?
I think I should use the Just() object in somehow, but I can't understand how, because the documentation at this stage is very unclear.
The main thing we can do to tighten up your code is to use .eraseToAnyPublisher() instead of AnyPublisher.init everywhere. This is the only real nitpick I have with your code. Using AnyPublisher.init is not idiomatic, and is confusing because it adds an extra layer of nested parentheses.
Aside from that, we can do a few more things. Note that what you wrote (aside from not using .eraseToAnyPublisher() appropriately) is fine, especially for an early version. The following suggestions are things I would do after I have gotten a more verbose version past the compiler.
We can use Optional's flatMap method to transform user.imageURL into a URL. We can also let Swift infer the Result type parameters, because we're using Result in a return statement so Swift knows the expected types. Hence:
func downloadUserProfilePhoto(user: User) -> AnyPublisher<UIImage?, StoreError> {
guard let url = user.imageURL.flatMap({ URL(string: $0) }) else {
return Result.Publisher(nil).eraseToAnyPublisher()
}
We can use mapError instead of catch. The catch operator is general: you can return any Publisher from it as long as the Success type matches. But in your case, you're just discarding the incoming failure and returning a constant failure, so mapError is simpler:
return NetworkService.getData(url: url)
.mapError { _ in .cantDownloadProfileImage }
We can use the dot shortcut here because this is part of the return statement. Because it's part of the return statement, Swift deduces that the mapError transform must return a StoreError. So it knows where to look for the meaning of .cantDownloadProfileImage.
The flatMap operator requires the transform to return a fixed Publisher type, but it doesn't have to return AnyPublisher. Because you are using Result<UIImage?, StoreError>.Publisher in all paths out of flatMap, you don't need to wrap them in AnyPublisher. In fact, we don't need to specify the return type of the transform at all if we change the transform to use Optional's map method instead of a guard statement:
.flatMap({ data in
UIImage(data: data)
.map { Result.Publisher($0) }
?? Result.Publisher(.cantDownloadProfileImage)
})
.eraseToAnyPublisher()
Again, this is part of the return statement. That means Swift can deduce the Output and Failure types of the Result.Publisher for us.
Also note that I put parentheses around the transform closure because doing so makes Xcode indent the close brace properly, to line up with .flatMap. If you don't wrap the closure in parens, Xcode lines up the close brace with the return keyword instead. Ugh.
Here it is all together:
func downloadUserProfilePhoto(user: User) -> AnyPublisher<UIImage?, StoreError> {
guard let url = user.imageURL.flatMap({ URL(string: $0) }) else {
return Result.Publisher(nil).eraseToAnyPublisher()
}
return NetworkService.getData(url: url)
.mapError { _ in .cantDownloadProfileImage }
.flatMap({ data in
UIImage(data: data)
.map { Result.Publisher($0) }
?? Result.Publisher(.cantDownloadProfileImage)
})
.eraseToAnyPublisher()
}
import Foundation
import Combine
enum AnyError<O>: Error {
case forcedError(O)
}
extension Publisher where Failure == Never {
public var limitedToSingleResponse: AnyPublisher<Output, Never> {
self.tryMap {
throw AnyError.forcedError($0)
}.catch { error -> AnyPublisher<Output, Never> in
guard let anyError = error as? AnyError<Output> else {
preconditionFailure("only these errors are expected")
}
switch anyError {
case let .forcedError(publishedValue):
return Just(publishedValue).eraseToAnyPublisher()
}
}.eraseToAnyPublisher()
}
}
let unendingPublisher = PassthroughSubject<Int, Never>()
let singleResultPublisher = unendingPublisher.limitedToSingleResponse
let subscription = singleResultPublisher.sink(receiveCompletion: { _ in
print("subscription ended")
}, receiveValue: {
print($0)
})
unendingPublisher.send(5)
In the snippet above I am converting a passthroughsubject publisher which can send a stream of values into something that stops after sending the first value. The essence of the snippet in based on the WWDC session about introduction to combine https://developer.apple.com/videos/play/wwdc2019/721/ here.
We are esentially force throwing an error in tryMap and then catching it with a resolving publisher using Just which as the question states will finish after the first value is subscribed to.
Ideally the demand is better indicated by the subscriber.
Another slightly more quirky alternative is to use the first operator on a publisher
let subscription_with_first = unendingPublisher.first().sink(receiveCompletion: { _ in
print("subscription with first ended")
}, receiveValue: {
print($0)
})
I'm using RxCoCoa and RxSwift for UITableView Biding.
the problem is when Connection lost or other connection errors except for Server Errors(I handled them) my app crash because of binding error that mentioned below. my question is how to handle Connection Errors?
fileprivate func getNextState() {
showFullPageState(State.LOADING)
viewModel.getProductListByID(orderGroup: OrderGroup.SERVICES.rawValue)
.do(onError: {
showStatusError(error: $0)
self.showFullPageState(State.CONTENT)
})
.filter {
$0.products != nil
}
.map {
$0.products!
}
.bind(to: (self.tableView?.rx.items(cellIdentifier: cellIdentifier, cellType: ProductCell.self))!) {
(row, element, cell) in
self.showFullPageState(State.CONTENT)
cell.product = element
}
.disposed(by: bag)
self.tableView?.rx.setDelegate(self).disposed(by: bag)
}
and this is my ViewModel :
func getProductListByID(orderGroup: String, page: String = "1", limit: String = "1000") -> Observable<ProductRes> {
return orderRegApiClient.getProductsById(query: getProductQueryDic(stateKey: getNextStateID(product: nextProduct)
, type: orderGroup, page: page, limit: limit)).map {
try JSONDecoder().decode(ProductRes.self, from: $0.data)
}.asObservable()
}
and I use Moya for my Network layer like This:
func getProductsById(query: [String: String]) -> Single<Response> {
return provider.rx.request(.getProductsById(query))
.filterSuccessfulStatusCodes()
}
You aren't handling errors anywhere. I mean you are acknowledging the error in the do operator but that doesn't actually handle it, that just allows it to pass through to the table view, which can't handle an error.
Look up the catchError series of operators for a solution. Probably .catchErrorJustReturn([]) will be all you need.
In a comment, you said:
... I don't want to return empty Array to my table. I want to show the error to customer and customer can retry service
In that case, you should use .catchError only for the success chain and setup a separate chain for the error as done below.
fileprivate func getNextState() {
showFullPageState(State.LOADING)
let products = viewModel.getProductListByID(orderGroup: OrderGroup.SERVICES.rawValue)
.share()
products
.catchError { _ in Observable.never() }
.filter { $0.products != nil }
.map { $0.products! }
.bind(to: tableView!.rx.items(cellIdentifier: cellIdentifier, cellType: ProductCell.self)) {
(row, element, cell) in
self.showFullPageState(State.CONTENT)
cell.product = element
}
.disposed(by: bag)
products
.subscribe(onError: { error in
showStatusError(error: error)
self.showFullPageState(State.CONTENT)
})
.disposed(by: bag)
self.tableView?.rx.setDelegate(self).disposed(by: bag)
}
The way you have the code setup, the only way for the user to retry the service is to call the function again. If you want to let the user retry in a more declarative manor, you would need to tie the chain to an observable that the user can trigger.
I am new to RxSwift and MVVM.
my viewModel has a method named rx_fetchItems(for:) that does the heavy lifting of fetching relevant content from backend, and returns Observable<[Item]>.
My goal is to supply an observable property of the viewModel named collectionItems, with the last emitted element returned from rx_fetchItems(for:), to supply my collectionView with data.
Daniel T has provided this solution that I could potentially use:
protocol ServerAPI {
func rx_fetchItems(for category: ItemCategory) -> Observable<[Item]>
}
struct ViewModel {
let collectionItems: Observable<[Item]>
let error: Observable<Error>
init(controlValue: Observable<Int>, api: ServerAPI) {
let serverItems = controlValue
.map { ItemCategory(rawValue: $0) }
.filter { $0 != nil }.map { $0! } // or use a `filterNil` operator if you already have one implemented.
.flatMap { api.rx_fetchItems(for: $0)
.materialize()
}
.filter { $0.isCompleted == false }
.shareReplayLatestWhileConnected()
collectionItems = serverItems.filter { $0.element != nil }.dematerialize()
error = serverItems.filter { $0.error != nil }.map { $0.error! }
}
}
The only problem here is that my current ServerAPI aka FirebaseAPI, has no such protocol method, because it is designed with a single method that fires all requests like this:
class FirebaseAPI {
private let session: URLSession
init() {
self.session = URLSession.shared
}
/// Responsible for Making actual API requests & Handling response
/// Returns an observable object that conforms to JSONable protocol.
/// Entities that confrom to JSONable just means they can be initialized with json.
func rx_fireRequest<Entity: JSONable>(_ endpoint: FirebaseEndpoint, ofType _: Entity.Type ) -> Observable<[Entity]> {
return Observable.create { [weak self] observer in
self?.session.dataTask(with: endpoint.request, completionHandler: { (data, response, error) in
/// Parse response from request.
let parsedResponse = Parser(data: data, response: response, error: error)
.parse()
switch parsedResponse {
case .error(let error):
observer.onError(error)
return
case .success(let data):
var entities = [Entity]()
switch endpoint.method {
/// Flatten JSON strucuture to retrieve a list of entities.
/// Denoted by 'GETALL' method.
case .GETALL:
/// Key (underscored) is unique identifier for each entity, which is not needed here.
/// value is k/v pairs of entity attributes.
for (_, value) in data {
if let value = value as? [String: AnyObject], let entity = Entity(json: value) {
entities.append(entity)
}
}
// Need to force downcast for generic type inference.
observer.onNext(entities as! [Entity])
observer.onCompleted()
/// All other methods return JSON that can be used to initialize JSONable entities
default:
if let entity = Entity(json: data) {
observer.onNext([entity] as! [Entity])
observer.onCompleted()
} else {
observer.onError(NetworkError.initializationFailure)
}
}
}
}).resume()
return Disposables.create()
}
}
}
The most important thing about the rx_fireRequest method is that it takes in a FirebaseEndpoint.
/// Conforms to Endpoint protocol in extension, so one of these enum members will be the input for FirebaseAPI's `fireRequest` method.
enum FirebaseEndpoint {
case saveUser(data: [String: AnyObject])
case fetchUser(id: String)
case removeUser(id: String)
case saveItem(data: [String: AnyObject])
case fetchItem(id: String)
case fetchItems
case removeItem(id: String)
case saveMessage(data: [String: AnyObject])
case fetchMessages(chatroomId: String)
case removeMessage(id: String)
}
In order to use Daniel T's solution, Id have to convert each enum case from FirebaseEndpoint into methods inside FirebaseAPI. And within each method, call rx_fireRequest... If I'm correct.
Id be eager to make this change if it makes for a better Server API design. So the simple question is, Will this refactor improve my overall API design and how it interacts with ViewModels. And I realize this is now evolving into a code review.
ALSO... Here is implementation of that protocol method, and its helper:
func rx_fetchItems(for category: ItemCategory) -> Observable<[Item]> {
// fetched items returns all items in database as Observable<[Item]>
let fetchedItems = client.rx_fireRequest(.fetchItems, ofType: Item.self)
switch category {
case .Local:
let localItems = fetchedItems
.flatMapLatest { [weak self] (itemList) -> Observable<[Item]> in
return self!.rx_localItems(items: itemList)
}
return localItems
// TODO: Handle other cases like RecentlyAdded, Trending, etc..
}
}
// Helper method to filter items for only local items nearby user.
private func rx_localItems(items: [Item]) -> Observable<[Item]> {
return Observable.create { observable in
observable.onNext(items.filter { $0.location == "LA" })
observable.onCompleted()
return Disposables.create()
}
}
If my approach to MVVM or RxSwift or API design is wrong PLEASE do critique.
I know it is tough to start understanding RxSwift
I like to use Subjects or Variables as inputs for the ViewModel and Observables or Drivers as outputs for the ViewModel
This way you can bind the actions that happen on the ViewController to the ViewModel, handle the logic there, and update the outputs
Here is an example by refactoring your code
View Model
// Inputs
let didSelectItemCategory: PublishSubject<ItemCategory> = .init()
// Outputs
let items: Observable<[Item]>
init() {
let client = FirebaseAPI()
let fetchedItems = client.rx_fireRequest(.fetchItems, ofType: Item.self)
self.items = didSelectItemCategory
.withLatestFrom(fetchedItems, resultSelector: { itemCategory, fetchedItems in
switch itemCategory {
case .Local:
return fetchedItems.filter { $0.location == "Los Angeles" }
default: return []
}
})
}
ViewController
segmentedControl.rx.value
.map(ItemCategory.init(rawValue:))
.startWith(.Local)
.bind(to: viewModel.didSelectItemCategory)
.disposed(by: disposeBag)
viewModel.items
.subscribe(onNext: { items in
// Do something
})
.disposed(by: disposeBag)
I think the problem you are having is that you are only going half-way with the observable paradigm and that's throwing you off. Try taking it all the way and see if that helps. For example:
protocol ServerAPI {
func rx_fetchItems(for category: ItemCategory) -> Observable<[Item]>
}
struct ViewModel {
let collectionItems: Observable<[Item]>
let error: Observable<Error>
init(controlValue: Observable<Int>, api: ServerAPI) {
let serverItems = controlValue
.map { ItemCategory(rawValue: $0) }
.filter { $0 != nil }.map { $0! } // or use a `filterNil` operator if you already have one implemented.
.flatMap { api.rx_fetchItems(for: $0)
.materialize()
}
.filter { $0.isCompleted == false }
.shareReplayLatestWhileConnected()
collectionItems = serverItems.filter { $0.element != nil }.dematerialize()
error = serverItems.filter { $0.error != nil }.map { $0.error! }
}
}
EDIT to handle problem mentioned in comment. You now need to pass in the object that has the rx_fetchItems(for:) method. You should have more than one such object: one that points to the server and one that doesn't point to any server, but instead returns canned data so you can test for any possible response, including errors. (The view model should not talk to the server directly, but should do so through an intermediary...
The secret sauce in the above is the materialize operator that wraps error events into a normal event that contains an error object. That way you stop a network error from shutting down the whole system.
In response to the changes in your question... You can simply make the FirebaseAPI conform to ServerAPI:
extension FirebaseAPI: ServerAPI {
func rx_fetchItems(for category: ItemCategory) -> Observable<[Item]> {
// fetched items returns all items in database as Observable<[Item]>
let fetchedItems = self.rx_fireRequest(.fetchItems, ofType: Item.self)
switch category {
case .Local:
let localItems = fetchedItems
.flatMapLatest { [weak self] (itemList) -> Observable<[Item]> in
return self!.rx_localItems(items: itemList)
}
return localItems
// TODO: Handle other cases like RecentlyAdded, Trending, etc..
}
}
// Helper method to filter items for only local items nearby user.
private func rx_localItems(items: [Item]) -> Observable<[Item]> {
return Observable.create { observable in
observable.onNext(items.filter { $0.location == "LA" })
observable.onCompleted()
return Disposables.create()
}
}
}
You should probably change the name of ServerAPI at this point to something like FetchItemsAPI.
You run into a tricky situation here because your observable can throw an error and once it does throw an error the observable sequence errors out and no more events can be emitted. So to handle subsequent network requests, you must reassign taking the approach you're currently taking. However, this is generally not good for driving UI elements such as a collection view because you would have to bind to the reassigned observable every time. When driving UI elements, you should lean towards types that are guaranteed to not error out (i.e. Variable and Driver). You could make your Observable<[Item]> to be let items = Variable<[Item]>([]) and then you could just set the value on that variable to be the array of items that came in from the new network request. You can safely bind this variable to your collection view using RxDataSources or something like that. Then you could make a separate variable for the error message, let's say let errorMessage = Variable<String?>(nil), for the error message that comes from the network request and then you could bind the errorMessage string to a label or something like that to display your error message.