Search code examples
uitableviewobservablebindrx-swift

How do I create an observable on core data and bind it to tableView


I fetch core data like this:

var persistingData: [MyDataObject] = coreData.fetchAll(fetchRequest: NSFetchRequest<MyDataObject>(entityName: "MyDataObject"))

Now how do I go about using this data for an Observable that can bind the data to an rx tableView?

Do I create an observable like this:

func fetchAllData() -> Observable<[MyDataObject]> {
    var persistingData: [MyDataObject] = coreData.fetchAll(fetchRequest: NSFetchRequest<MyDataObject>(entityName: "MyDataObject"))

    return Observable.create({ observable in
        observable.onNext(persistingData)
        observable.onCompleted()
        return Disposables.create()
    })
}

But then how I use bind() on the observable if I pass the data to onNext? bind is used directly on the method like so:

fetchAllData().bind()

How do I actually get the data into the observable so that it can be used in bind()?

EDIT

I tried like this as well. Is this a valid way of doing it?

func fetchAllData() -> PublishRelay<[MyDataObject]> {
    var persistingData: [MyDataObject] = coreData.fetchAll(fetchRequest: NSFetchRequest<MyDataObject>(entityName: "MyDataObject"))

    let relay = PublishRelay<[LocalDoorCoreDataObject]>()
    relay.accept(persistingDoors)
    return relay
}

Then you can bind to it like this:

viewModel.fetchAllData().bind(to: tableView.rx.items(cellIdentifier: Cell.identifier, cellType: Cell.self)) { row, data, cell in

    cell.viewModel = data

}.disposed(by: disposeBag)

Does this seem reasonable, or is there some other way?


Solution

  • Rx is particularly useful when handling streams of data, but you are trying to use it as a simple wrapper around a single Core Data fetch request. I'd build a BehaviorRelay to keep a copy of the data coming from Core Data, and an Observable that runs your fetch request and reports values to the BehaviorRelay that you can subscribe to any time you need to refresh.

    Having in your view model:

    let data = BehaviorRelay<[MyDataObject]>(value: [])
    

    I would bind it to your table view:

    viewModel.data.bind(to: tableView.rx.items(cellIdentifier: Cell.identifier, cellType: Cell.self)) { row, data, cell in
        cell.viewModel = data
    
    }.disposed(by: disposeBag)
    

    And then, back in your view model:

    var loadData: Observable<[MyDataObject]> {
       return Observable.deferred { [unowned self] in
          return Observable.just(self.coreData.fetchAll(fetchRequest: NSFetchRequest<MyDataObject>(entityName: "MyDataObject")))
       }.do(onNext: { [unowned self] data in
          self.data.accept(data)
       }
    }
    

    Anytime you need to refresh data, you just call:

    viewModel.loadData.subscribe().disposed(by: disposeBag)
    

    You can use the value emitted by loadData to make immediate changes to your UI, such as displaying an empty screen or stopping the loading indicator. If you do, make sure to add observeOn(MainScheduler.instance) before subscribing.