Search code examples
iosswiftuitableviewrx-swiftmoya

Not getting Completed event on RxSwift with RxMoya


The following code works:

    let provider = RxMoyaProvider<MyAPI>( stubClosure: MoyaProvider.delayedStub(3))

    provider
        .request(.studentSearch(query: ""))
        .retry(3)
        .observeOn(MainScheduler.instance)
        .asObservable()
        .mapJSON()
        .map { respJSON in
            guard let studentsJsonArray = JSON(respJSON)["students"].array else {
                throw APIError.wrongJSONParsing
            }
            return studentsJsonArray.map {
                guard let students = Student.fromJSON($0) else {
                    fatalError("Invalid Student Object")
                }
                return students
                } as [Student]
        }
        .subscribe(onNext: {
          print($0)
        }, onCompleted: {
          print($0) // This one is being called.
        })
        .disposed(by: rx.disposeBag)

The method onCompleted is being called in the above code But not in the following one.

I am trying to do it with refresh trigger like the one in the UITableView for refreshing the content. I want to load the contents on start so I use startWith(()) in the following code in my ViewModel

let results: Driver<[Student]>
var refreshTrigger = PublishSubject<Void>()
results = refreshTrigger
        .startWith(())
        .do(onNext: {
            execute.value = true
        })
        .flatMapLatest {
            provider
                .request(.studentSearch(query: ""))
                .retry(3)
                .observeOn(MainScheduler.instance)
                .asObservable()
        }
        .mapJSON()
        .map { respJSON in
            guard let studentsJsonArray = JSON(respJSON)["students"].array else {
                throw APIError.wrongJSONParsing
            }
            return studentsJsonArray.map {
                guard let students = Student.fromJSON($0) else {
                    fatalError("Invalid Student Object")
                }
                return students
            }
        }
        .do(onNext: {
            items.value = $0
            execute.value = false
            noResults.value = items.value.isEmpty
        }, onCompleted: {
            print($0)
        })
        .asDriver(onErrorJustReturn: [])

and in the controller I call following in viewdidload.

viewModel
        .results
        .asObservable()
        .map { StudentGroup(header: "Follower", items: $0) }
        .subscribe(onNext: {
            print($0)
        }, onCompleted: {
            print($0) // This is not being called.
        })
        .disposed(by: rx.disposeBag)

Here onCompleted is not being called. I don't know the reason why?. Please help me out.


Solution

  • flatMapLatest will not send an onCompleted event unless its input (the PublishSubject in your case) completes. The Moya requests send their own onCompleted events alright, but those are filtered out when flatMapLatest merges the results. In other words, there is a long-lived subscription from the PublishSubject to the data source which doesn't complete, and it's not supposed to, either. (edited) (Otherwise you'd lose the refresh functionality after the first load.)

    I got help from Zsolt Varadi from RxSwift Slack channel.

    here is the solution

    results = refreshTrigger
            .startWith(())
            .do(onNext: {
                execute.value = true
            })
            .flatMapLatest {
                provider
                    .request(.studentSearch(query: ""))
                    .retry(3)
                    .observeOn(MainScheduler.instance)
                    .asObservable()
                    .mapJSON()
                    .map { respJSON in
                        guard let studentsJsonArray = JSON(respJSON)["students"].array else {
                            throw APIError.wrongJSONParsing
                        }
                        return studentsJsonArray.map {
                            guard let students = Student.fromJSON($0) else {
                                fatalError("Invalid Student Object")
                            }
                            return students
                            } as [Student]
                    }
                    .map { StudentGroup(header: "Follower", items: $0) }
                    .toArray()
                    .catchErrorJustReturn([])
                    .do(onNext: {
                        studentGroups.value = $0
                        execute.value = false
                        guard let isEmpty = studentGroups.value.first?.items.isEmpty else {
                            return
                        }
                        noResults.value = isEmpty
                    })
            }
            .asDriver(onErrorJustReturn: [])