4

I can't manage to get this solution to work: https://github.com/liuznsn/RxMoyaPaginationNetworking

Maybe someone can tell me where is the mistake. The loading variable never goes to false. I guess the issue is in the request observable, but I can't find out why.

class PaginationNetworkModel<T1: Mappable>: NSObject {

let refreshTrigger = PublishSubject<Void>()
let loadNextPageTrigger = PublishSubject<Void>()
let loading = Variable<Bool>(false)
let elements = Variable<[T1]>([])
var offset:Int = 0
let error = PublishSubject<Swift.Error>()

private let disposeBag = DisposeBag()

override init() {
    super.init()

    let refreshRequest = loading.asObservable()
        .sample(refreshTrigger)
        .flatMap { [unowned self] loading -> Observable<[T1]> in
            if loading {
                return Observable.empty()
            } else {
                return self.loadData(offset: self.offset)
            }
    }

    let nextPageRequest = loading.asObservable()
        .sample(loadNextPageTrigger)
        .flatMap { [unowned self] loading -> Observable<[T1]> in
            if loading {
                return Observable.empty()
            } else {
                self.offset += 1
                return self.loadData(offset: self.offset)
            }
    }

    let request = Observable
        .of(refreshRequest, nextPageRequest)
        .merge()
        .shareReplay(1)

    let response = request.flatMap { events -> Observable<[T1]> in
        request
            .do(onError: { error in
                self.error.onNext(error)
            }).catchError({ error -> Observable<[T1]> in
                Observable.empty()
            })
    }.shareReplay(1)

    Observable
        .combineLatest(request, response, elements.asObservable()) { [unowned self] request, response, elements in
            return self.offset == 0 ? response : elements + response
        }
        .sample(response)
        .bind(to: elements)
        .addDisposableTo(rx_disposeBag)

    Observable
        .of(request.map { _ in true },
            response.map { $0.count == 0 },
            error.map { _ in false }
        )
        .merge()
        .bind(to: loading)
        .addDisposableTo(rx_disposeBag)
}

func loadData(offset: Int) -> Observable<[T1]> {
    return Observable.empty()
}
Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
pluck
  • 179
  • 2
  • 13
  • Do you realize that your `loadData` function never returns any data which means `response` never fires? – Daniel T. Apr 16 '17 at 19:32
  • Yeah. As you could see it generic class, and if I need other server method I inherit this class. I'm sure response fires, i.e. if I set request.map to false I will get all objects from server, but without "loading" feature. – pluck Apr 17 '17 at 06:22
  • For some reason request.map called after response.map, where sets false, and immediately sets to true coz of request, but I can't see where it get called after response – pluck Apr 17 '17 at 07:28

2 Answers2

7

Thank you Daniel for help, here completed solution

Call example:

goodsModel = GoodsNetworkModel()
    goodsModel.elements.asObservable().bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: StoreCell.self)) { (ip, item: Goods, cell: StoreCell) in
        cell.configure(goods: item)
    }.addDisposableTo(rx_disposeBag)

    tableView.rx.itemSelected.bind() { [unowned self] ip in
        self.didSelectRow(ip: ip.row)
    }.addDisposableTo(rx_disposeBag)

    rx.sentMessage(#selector(UIViewController.viewDidAppear(_:)))
        .map { _ in () }
        .bind(to: goodsModel.refreshTrigger)
        .addDisposableTo(rx_disposeBag)

    tableView.rx_reachedBottom
        .map{ _ in ()}
        .bind(to: goodsModel.loadNextPageTrigger)
        .addDisposableTo(rx_disposeBag)

Model code:

class PaginationNetworkModel<T1: Mappable>: NSObject {

let refreshTrigger = PublishSubject<Void>()
let loadNextPageTrigger = PublishSubject<Void>()
let loading = Variable<Bool>(false)
let elements = Variable<[T1]>([])
var offset:Int = 0
let error = PublishSubject<Swift.Error>()

private let disposeBag = DisposeBag()

override init() {
    super.init()

    let refreshRequest = loading.asObservable()
        .sample(refreshTrigger)
        .flatMap { loading -> Observable<Int> in
            if loading {
                return Observable.empty()
            } else {
                return Observable<Int>.create { observer in
                    observer.onNext(0)
                    observer.onCompleted()
                    return Disposables.create()
                }
            }
    }

    let nextPageRequest = loading.asObservable()
        .sample(loadNextPageTrigger)
        .flatMap { [unowned self] loading -> Observable<Int> in
            if loading {
                return Observable.empty()
            } else {
                return Observable<Int>.create { [unowned self] observer in
                    self.offset += 1
                    observer.onNext(self.offset)
                    observer.onCompleted()
                    return Disposables.create()
                }
            }
    }

    let request = Observable
        .of(refreshRequest, nextPageRequest)
        .merge()
        .shareReplay(1)

    let response = request.flatMap { offset -> Observable<[T1]> in
        self.loadData(offset: offset)
            .do(onError: { [weak self] error in
                self?.error.onNext(error)
            }).catchError({ error -> Observable<[T1]> in
                Observable.empty()
            })
        }.shareReplay(1)

    Observable
        .combineLatest(request, response, elements.asObservable()) { [unowned self] request, response, elements in
            return self.offset == 0 ? response : elements + response
        }
        .sample(response)
        .bind(to: elements)
        .addDisposableTo(rx_disposeBag)

    Observable
        .of(request.map{_ in true},
            response.map { $0.count == 0 },
            error.map { _ in false })
        .merge()
        .bind(to: loading)
        .addDisposableTo(rx_disposeBag)
}

func loadData(offset: Int) -> Observable<[T1]> {
    return Observable.empty()
}
pluck
  • 179
  • 2
  • 13
  • If I create a `PaginationNetworkModel` then trigger its `loadNextPageTrigger`, the model will attempt to load page 1 without ever attempting to load page 0. Are you sure this is correct behavior? – Daniel T. Apr 18 '17 at 13:13
  • 1
    Edit my answer, so there before calling loadNextPageTrigger I always call refreshTrigger – pluck Apr 18 '17 at 13:34
5

The problem is here:

    let refreshRequest: Observable<[T1]> = loading.asObservable()
        .sample(refreshTrigger)
        .flatMap { [unowned self] loading -> Observable<[T1]> in
            if loading {
                return Observable.empty()
            } else {
                return self.loadData(offset: self.offset)
            }
    }

refreshRequest doesn't emit a value until after loadData returns. The way your code is structured, emitting a signal on the refreshTrigger will start the network request, and then set loading to true after the network request completes.

It would be better to have refreshRequest and nextPageRequest return an Observable of what page to load and then merge them and call flatMap with the network call on the merged result.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72