Search code examples
swiftrx-swiftmoya

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()
}

Solution

  • 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.