1

Currently for our API requests we use Rx. An example of how we use it is:

let orderRxService = OrderRxService.listAsShop(shopId, status: .active)
    .repeatRequest(delay: 4)
    .observeOn(MainScheduler.instance)
    .subscribe( onNext: { [weak self] orders in
        self?.orders = orders
        self?.tableView.reloadData()
    })
    .disposed(by: disposeBag)

This gets all orders for given shopId with the status .active. On every update the local orders object is replaced and the tableView is reloaded.

This reload the whole tableView, which we wanna avoid. I'm now looking into RxDataSources but can't really figure out what is the way to get this working.

An Order object has another property currentStatus, which can be 3 different values. What we have is a tableView with 3 different sections, each section displaying all orders for a currentStatus.

How should this be implemented in RxDataSources? Ideally would be to bind it to the service I showed earlier (OrderRxService.....subscribe()..).

What I have now to setup the RxDataSources-types is:

extension Order: IdentifiableType, Equatable {
    public typealias Identity = String

    public var identity: String {
        return String(id)
    }

    public static func == (lhs: Order, rhs: Order) -> Bool {
        return (lhs.timeCreated ?? 0) > (rhs.timeCreated ?? 0)
    }
}

struct OrdersSection {
    var header: String
    var orders: [Order]
}

extension OrdersSection: AnimatableSectionModelType {
    typealias Item = Order
    typealias Identity = String

    var identity: String {
        return header
    }

    var items: [Item] {
        set {
            orders = items
        }
        get {
            return orders
        }
    }

    init(original: OrdersSection, items: [Order]) {
        self = original
        self.items = items
    }
}

What I tried to make it work is:

// I tried to make our local orders a Variable (I don't like this between-step and would like this to be just [Order]).
var orders: Variable<[Order]> = Variable([])


fun viewDidLoad() {
    super.viewDidLoad()

    // Then I set the local orders-variable's value to the new value coming from our Rx service.
    let orderRxDisposable: Disposable = OrderRxService.listAsShop(shopId, status: .active)
        .repeatRequest(delay: 4)
        .observeOn(MainScheduler.instance)
        .map { $0.items }.subscribe( onNext: { [weak self] orders in
            self?.orders.value = orders
        })

    // Here I setup the dataSource
    let dataSource = RxTableViewSectionedAnimatedDataSource<OrdersSection>(
        configureCell: { ds, tv, ip, item in
            let cell = tv.dequeueReusableCell(withIdentifier: "OrderCell", for: ip) as! OrderCell
            cell.addContent(item, tableView: tv, viewController: self, spotDelegate: self)
            return cell
        },

        titleForHeaderInSection: { ds, ip in
            return ds.sectionModels[ip].header
        }
    )

    // Here I set up the three different sections.
    self.orders.asObservable().observeOn(MainScheduler.instance)
        .map { o in
            o.filter { $0.currentStatus == .status_one }
        }
        .map { [OrdersSection(header: "Status one", orders: $0)] }
        .bind(to: self.tableView.rx.items(dataSource: dataSource))

    self.orders.asObservable().observeOn(MainScheduler.instance)
        .map { o in
            o.filter { $0.currentStatus == .status_two }
        }
        .map { [OrdersSection(header: "Status two", orders: $0)] }
        .bind(to: self.tableView.rx.items(dataSource: dataSource))

    self.orders.asObservable().observeOn(MainScheduler.instance)
        .map { o in
            o.filter { $0.currentStatus == .status_three }
        }
        .map { [OrdersSection(header: "Status three", orders: $0)] }
        .bind(to: self.tableView.rx.items(dataSource: dataSource))

}

There are probably different aspects that can be improved. For example the Variable<[Order]> I would like to be just [Order]. And instead of making this observable, could that be skipped altogether and create the three different sections by observing our OrderRxService?

Would it be possible to have it something like:

OrderRxService.listAsshop(shopId, status: .active).observeOn(MainScheduler.instance)
    // First section
    .map { o in
        o.filter { $0.status == .status_one }
    }
    .map { [OrdersSection(header: "Status one", orders: $0)] }
    .bind(to: self.tableView.rx.items(dataSource: dataSource))
    // Second section
    .map { o in
        o.filter { $0.status == .status_two }
    }
    .map { [OrdersSection(header: "Status two", orders: $0)] }
    .bind(to: self.tableView.rx.items(dataSource: dataSource))
    // Etc...

Thanks for any help!

Jeroen
  • 2,011
  • 2
  • 26
  • 50

2 Answers2

5

You could create a model like so:

enum SectionModel {
  case SectionOne(items: [SectionItem])
  case SectionTwo(items: [SectionItem])
  case SectionThree(items: [SectionItem])
}

enum SectionItem {
  case StatusOne()
  case StatusTwo()
  case StatusThree()
}

extension SectionModel: SectionModelType {
  typealias Item = SectionItem

  var items: [SectionItem] {
      switch self {
      case .SectionOne(items: let items):
          return items.map { $0 }
      case .SectionTwo(items: let items):
          return items.map { $0 }
      case.SectionThree(items: let items):
          return items.map { $0 }
      }
  }

  init(original: SectionModel, items: [Item]) {
      switch  original {
      case .SectionOne(items: _):
          self = .SectionOne(items: items)
      case .SectionTwo(items: _):
          self = .SectionTwo(items: items)
      case .SectionThree(items: _):
          self = .SectionThree(items: items)
      }
  }
}

and handle the different items in your datasource

dataSource = RxCollectionViewSectionedReloadDataSource<SectionModel>(configureCell: { (datasource, collectionView, indexPath, _) in
        switch datasource[indexPath] {
        case .StatusOne:
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: R.reuseIdentifier.statusCellOne, for: indexPath)!
            // do stuff
            return cell
        case .StatusTwo:
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: R.reuseIdentifier.statusCellTwo, for: indexPath)!
            // do stuff
            return cell
        case .StatusThree:
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: R.reuseIdentifier.statusCellThree, for: indexPath)!
            // do stuff
            return cell
        }
    })

and then map your oders to the SectionItem for the SectionModel and bind it to the dataSource

zero3nna
  • 2,770
  • 30
  • 28
  • Thanks for your extended answer! I feel this is a step in the right direction. Just a few questions until I get the concept (by the way, I have a `tableView`, but I guess that doesn't change much). Where in this do I see `Order`? I don't get where the `[Order]` is filtered into the 3 different ones. I see there are 3 statuses, but I can't relate it to `Order.status`. It seems like `SectionItem` should be changed to `Order` to solve my problem? Or is that not correct? – Jeroen Mar 27 '19 at 15:01
  • 1
    And I suppose `RxCollectionViewSectionedReloadDataSource` could be `RxTableViewSectionedReloadDataSource`? :-) – Jeroen Mar 27 '19 at 15:05
0

You don't need to map->bind->map->bind... You can simply handle it all in one 'map' like so:

OrderRxService.listAsshop(shopId, status: .active).observeOn(MainScheduler.instance)
    .map { orders in
        let sections: [OrdersSection] = []

        sections.append(OrdersSection(header: "Status one", orders: orders.filter { $0.status == .status_one })
        
        sections.append(OrdersSection(header: "Status two", orders: orders.filter { $0.status == .status_two })
 
        sections.append(OrdersSection(header: "Status three", orders: orders.filter { $0.status == .status_three })
        
        return sections
    }
    .bind(to: self.tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)

Also, if you use RxSwift/RxDataSources/etc quite a bit and need guidance, joining the RxSwift Slack channel is a great resource: Invite to RxSwift Slack

Weston Mitchell
  • 192
  • 1
  • 12