Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxSwift pagination

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()
}
like image 301
pluck Avatar asked Dec 14 '22 23:12

pluck


2 Answers

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()
}
like image 142
pluck Avatar answered Dec 16 '22 13:12

pluck


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.

like image 31
Daniel T. Avatar answered Dec 16 '22 14:12

Daniel T.