Zigii Wong
Zigii Wong

Reputation: 7826

RefreshNextPage with RxSwift

I am using RxSwift to make a pull to refresh and refreshNextPage.

Currently, here is the viewModel I work so far:

public final class MomentViewModel {
// Property
let refreshTrigger = PublishSubject<Void>()
let loadNextPageTrigger = PublishSubject<Void>()
let loading = Variable<Bool>(false)
let posts = Variable<[Post]>([])
var pageIndex: Int = 0
let error = PublishSubject<Swift.Error>()
private let disposeBag = DisposeBag()

public init() {

    let refreshRequest = loading.asObservable()
        .sample(refreshTrigger)
        .flatMap { loading -> Observable<Int> in
            if loading {
                return Observable.empty()
            } else {
                return Observable<Int>.create { observer in
                    self.pageIndex = 0
                    print("reset page index to 0")
                    observer.onNext(0)
                    observer.onCompleted()
                    return Disposables.create()
                }
            }
    }
    .debug("refreshRequest", trimOutput: false)

    let nextPageRequest = loading.asObservable()
        .sample(loadNextPageTrigger)
        .flatMap { loading -> Observable<Int> in
            if loading {
                return Observable.empty()
            } else {
                return Observable<Int>.create { [unowned self] observer in
                    self.pageIndex += 1
                    print(self.pageIndex)
                    observer.onNext(self.pageIndex)
                    observer.onCompleted()
                    return Disposables.create()
                }
            }
    }
    .debug("nextPageRequest", trimOutput: false)

    let request = Observable.merge(refreshRequest, nextPageRequest)
        .debug("Request", trimOutput: false)

    let response = request.flatMapLatest { page in
            RxAPIProvider.shared.getPostList(page: page).materialize()
        }
        .share(replay: 1)
        .elements()
        .debug("Response", trimOutput: false)

    Observable
        .combineLatest(request, response, posts.asObservable()) { request, response, posts in
            return self.pageIndex == 0 ? response : posts + response
        }
        .sample(response)
        .bind(to: posts)
        .disposed(by: disposeBag)

    Observable
        .merge(request.map{ _ in true },
            response.map { _ in false },
            error.map { _ in false })
        .bind(to: loading)
        .disposed(by: disposeBag)
    }
}

The refreshTrigger and loadNextPageTrigger is bind to difference target likes:

self.tableView.rx_reachedBottom
        .map { _ in () }
        .bind(to: self.viewModel.loadNextPageTrigger)
        .disposed(by: disposeBag)

self.refreshControl.rx.controlEvent(.valueChanged)
        .bind(to: self.viewModel.refreshTrigger)
        .disposed(by: disposeBag)


self.rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
        .map { _ in () }
        .bind(to: viewModel.refreshTrigger)
        .disposed(by: disposeBag)

Question:

When I scroll the tableView to bottom and trigger the loadNextPageTrigger, everything works fine.

However, if there is no more data in the next coming request, the loadnextPageTrigger will be triggered infinitely. Any help would be appreciated.

You can download the Demo here.

Upvotes: 0

Views: 1085

Answers (1)

pacification
pacification

Reputation: 6018

Main idea is to check if all elements is loaded. I found in source code that you load parts by 20, so this condition should works fine if loadedPosts < 20 then stop loading new part. Here i share with you basic solution that you can refactor as you want (because i'm not good at RxSwift):

In MomentViewModel you should declare property

private var isAllLoaded = false

that set to true if you loaded all values. Then you should check every [Post] that came in response to set correct isAllLoaded:

Observable
    .combineLatest(request, response, posts.asObservable()) { request, response, posts in
        self.isAllLoaded = response.count < 20 // here the check
        return self.pageIndex == 0 ? response : posts + response
    }
    .sample(response)
    .bind(to: posts)
    .disposed(by: disposeBag)

And then in nextPageRequest in you should return .empty() observer if all parts have been loaded:

if loading {
    return Observable.empty()
} else {
    guard !self.isAllLoaded else { return Observable.empty() }

    return Observable<Int>.create { [unowned self] observer in
        self.pageIndex += 1
        print(self.pageIndex)
        observer.onNext(self.pageIndex)
        observer.onCompleted()
        return Disposables.create()
    }
}  

P.S. The MomentViewModel.swift file to copy/paste.

Upvotes: 1

Related Questions