1
votes

I am trying to set up a tableview that refreshes user data after a button is pressed. RXSwift is used for the entire chain of events. Moya is used for routing.

I am trying to use the standard error handling given by Moya, which is:

provider.rx.request(.userProfile("ashfurrow")).subscribe { event in
    switch event {
    case let .success(response):
        image = UIImage(data: response.data)
    case let .error(error):
        print(error)
    }
}

The only way I have been able to get this to work, is to use an inner subscribe method. Please see code below. Can anyone think of a way that does not require an inner subscribe? It seems a bit clumsy as is.

class ViewController: UIViewController {
    @IBOutlet weak var refreshBtn: UIButton!
    @IBOutlet weak var tableView: UITableView!

    let provider = MoyaProvider<MyAPI>()

    let disposeBag = DisposeBag()

    var latestUsers = Variable<[User]>([])

    override func viewDidLoad() {
        super.viewDidLoad()

        setupObservableBtnRefreshWithDataFetch()
        bindDataToTableView()
    }

    func setupObservableBtnRefreshWithDataFetch() {
        let refreshStream = refreshBtn.rx.tap.startWith(())

        let responseStream = refreshStream.flatMapLatest { _ -> SharedSequence<DriverSharingStrategy, [User]> in
            let request = self.provider.rx.request(.showUsers)

            // Inner Subscribe here, to be able to use the standard Moya subscribe methods for error handling
            request.subscribe { event in
                switch event {
                case .success(let user):
                    print("Success")
                case .error(let error):
                    print("Error occurred: \(error.localizedDescription)")
                }
            }

            return request
                .filterSuccessfulStatusCodes()
                .map([User].self)
                .asDriver(onErrorJustReturn: [])
        }

        let nilOnRefreshTapStream: Observable<[User]> = refreshBtn.rx.tap.map { _ in return [] }
        let tableDisplayStream = Observable.of(responseStream, nilOnRefreshTapStream)
            .merge()
            .startWith([])

        tableDisplayStream
            .subscribe { event in
                switch event {
                case .next(let users):
                    print("Users are:")
                    print(users)
                    self.latestUsers.value = users
                    break
                case .completed:
                    break
                case .error(let error):
                    print("Error occurred: \(error.localizedDescription)")
                    break
                }
            }
            .disposed(by: self.disposeBag)
    }

    func bindDataToTableView() {
        latestUsers.asObservable()
            .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { (_, model: User, cell: UITableViewCell) in
                cell.textLabel?.text = model.login
            }
            .disposed(by: disposeBag)
    }
}      

class User: Decodable {
    var name: String?
    var mobile: Int?
    var userRequestedTime: String?
    var login: String?

    init(name: String, mobile: Int, login: String = "") {
        self.name = name
        self.mobile = mobile
        self.login = login
    }
}
1
I am not familiar with Moya, but the double subscribe to request (both your manual subscribe and any subscription to the return value of refreshStream.flatMap) don't seem right. What happens if you omit the manual subscribe?CloakedEddy
the fields available in the subscription event changes (like .success changes to complete). Works well as I have done it though.Rowan Gontier

1 Answers

0
votes

I have investigated Moya and learned it is a wrapper for network operations.

It's not entirely clear to what purpose the inner subscribe serves - based on my understanding, it triggers an identical but separate network request, which should not affect the other request subscription. It also seems like the refreshButton tap emits two elements in tableDisplayStream (from responseStream (from refreshStream) and nilOnRefreshTapStream).

Note that Variable is deprecated. Personally, I also prefer .debug().subscribe() to manually printing events in the subscription closure.

Based on this, I would write the code as follows instead. I have not tested it. Hope it helps!

class ViewController: UIViewController {
    // ...

    private let provider = MoyaProvider<MyAPI>()
    private let disposeBag = DisposeBag()

    /// Variable<T> is deprecated; use BehaviorRelay instead
    private let users = BehaviorRelay<[User]>(value: [])

    private func setupObservableBtnRefreshWithDataFetch() {
        refreshBtn.rx.tap
           .startWith(()) // trigger initial load
           .flatMapLatest { _ in 
               self.provider.rx.request(.showUsers)
                   .debug("moya request")
                   .filterSuccessfulStatusCodes()
                   .map([User].self)
                   .asDriver(onErrorJustReturn: []) // don't let the error escape
           } 
           .drive(users)
           .disposed(by: disposeBag)
    }

    private func bindDataToTableView() {
        users
            .asDriver()
            .debug("driving table view ")
            .drive(tableView.rx.items /* ... */)
            .disposed(by: disposeBag)
    }
}