简体   繁体   中英

How to bind data from viewModel in view with rxSwift and Moya?

I'm trying to create an app to get some news from an API and i'm using Moya, RxSwift and MVVM. This is my ViewModel:

import Foundation
import RxSwift
import RxCocoa

public enum NewsListError {
    case internetError(String)
    case serverMessage(String)
}

enum ViewModelState {
    case success
    case failure
}

protocol NewsListViewModelInput {
    func viewDidLoad()
    func didLoadNextPage()
}

protocol MoviesListViewModelOutput {
    var newsList: PublishSubject<NewsList> { get }
    var error: PublishSubject<String> { get }
    var loading: PublishSubject<Bool> { get }
    var isEmpty: PublishSubject<Bool> { get }
}

protocol NewsListViewModel: NewsListViewModelInput, MoviesListViewModelOutput {}

class DefaultNewsListViewModel: NewsListViewModel{

    func viewDidLoad() {

    }

    func didLoadNextPage() {

    }


    private(set) var currentPage: Int = 0
    private var totalPageCount: Int = 1

    var hasMorePages: Bool {
        return currentPage < totalPageCount
    }
    var nextPage: Int {
        guard hasMorePages else { return currentPage }
        return currentPage + 1
    }

    private var newsLoadTask: Cancellable? { willSet { newsLoadTask?.cancel() } }

    private let disposable = DisposeBag()

    // MARK: - OUTPUT
    let newsList: PublishSubject<NewsList> = PublishSubject()
    let error: PublishSubject<String> = PublishSubject()
    let loading: PublishSubject<Bool> = PublishSubject()
    let isEmpty: PublishSubject<Bool> = PublishSubject()

    func getNewsList() -> Void{
        print("sono dentro il viewModel!")
        NewsDataService.shared.getNewsList()
            .subscribe { event in
                switch event {
                case .next(let progressResponse):
                    if progressResponse.response != nil {
                        do{
                            let json = try progressResponse.response?.map(NewsList.self)
                            print(json!)
                            self.newsList.onNext(json!)
                        }
                        catch _ {
                            print("error try")
                        }
                    } else {
                        print("Progress: \(progressResponse.progress)")
                    }
                case .error( _): break
                // handle the error
                default:
                    break
                }
        }
    }

}

This is my ViewController, where xCode give me the following error when i try to bind to tableNews:

Expression type 'Reactive<_>' is ambiguous without more context
import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {

    @IBOutlet weak var tableNews: UITableView!

    let viewModel = DefaultNewsListViewModel()

    var disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    private func setupBindings() {

        viewModel.newsList.bind(to: tableNews.rx.items(cellIdentifier: "Cell")) {
            (index, repository: NewsList, cell) in
            cell.textLabel?.text = repository.name
            cell.detailTextLabel?.text = repository.url
        }
        .disposed(by: disposeBag)
    }
}

This is the service that get data from API:


import Moya
import RxSwift
struct NewsDataService {
    static let shared = NewsDataService()

    private let disposable = DisposeBag()

    private init() {}

    fileprivate let newsListProvider = MoyaProvider<NewsService>()

    func getNewsList() -> Observable<ProgressResponse> {
        self.newsListProvider.rx.requestWithProgress(.readNewsList)
        }
    }

I'm new at rxSwift, I followed some documentation but i'd like to know if i'm approaching in the right way. Another point i'd like to know is how correctly bind my tableView to viewModel. Thanks for the support.

As @FabioFelici mentioned in the comments, UITableView.rx.items(cellIdentifier:) is expecting to be bound to an Observable that contains an array of objects but your NewsListViewModel.newsList is an Observable<NewsList> .

This means you either have to extract the array out of NewsList (assuming it has one) through a map. As in newsList.map { $0.items }.bind(to: ...

Also, your MoviesListViewModelOutput should not be full of Subjects, rather it should contain Observables. And I wouldn't bother with the protocols, strut s are fine.

Also, your view model is still very imperative, not really in an Rx style. A well constructed Rx view model doesn't contain functions that are repeatedly called. It just has a constructor (or is itself just a single function.) You create it, bind to it and then you are done.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM