3
\$\begingroup\$

Below I have enum representing State of a UITableView. The states are divided into 3 categories as below.

For all the 3 states I have different UITableViewCells which will be shown for respected cases.

So for fetchMovies input, I am actually checking the movies count and emitting the state & once again after the result.

Can the code written in transform be further reduced or any suggestions.

You can find the architecture here CleanArchitectureRxSwift

import Foundation
import RxSwift
import RxCocoa

protocol ViewModelType {
  associatedtype Input
  associatedtype Output
  
  func transform(input: Input) -> Output
}

enum MoviesViewModelState {
  case loading
  case loaded([String])
  case loadRemaining
}

class MoviesViewModel: ViewModelType {
  
  deinit {
    print("HomeViewModel deinit")
  }
  
  struct Input {
    let fetchMovies: Driver<Void>
  }
  
  struct Output {
    let movieSection: Driver<MoviesViewModelState>
  }
  
  var repository: SyncRepository
  private var names: [String] = []
  private let disposeBag = DisposeBag()
  
  init(repository: SyncRepository) {
    self.repository = repository
  }
  
  func transform(input: Input) -> Output {
    let movieSection = PublishSubject<MoviesViewModelState>()
    
    input.fetchMovies
      .asObservable()
      .flatMapLatest { [unowned self] _ -> Observable<[String]> in
        if self.names.count > 0 {
          movieSection.onNext(.loadRemaining)
        } else {
          movieSection.onNext(.loading)
        }
        return self.repository.fetchNames(for: self.names.count) // returns Observable<[String]>
    }.subscribe(onNext: { [unowned self] (names: [String]) in
      self.names.append(contentsOf: names)
      movieSection.onNext(.loaded(self.names))
    }).disposed(by: disposeBag)
    
    return Output(movieSection: movieSection.asDriverOnErrorJustComplete())
  }
}

extension ObservableType {
  func asDriverOnErrorJustComplete() -> Driver<Element> {
    return asDriver { _ in
      return Driver.empty()
    }
  }
}
\$\endgroup\$
1
  • \$\begingroup\$ I'm not a big fan of the architecture. It's a lot of boilerplate just to implement a single function. \$\endgroup\$ – Daniel T. Nov 12 '20 at 2:24
1
\$\begingroup\$

Well, the fact that the result of the network request is needed in the next network request means you have a cycle which mean you have to have a subject of some sort. Also, you have to maintain state which implies a BahviorSubject rather than a PublishSubject.

That said, whenever you have an onNext inside a subscribe, there is likely a cleaner way.

class MoviesViewModel: ViewModelType {

    struct Input {
        let fetchMovies: Observable<Void>
    }

    struct Output {
        let movieSection: Driver<MoviesViewModelState>
    }

    private let repository: SyncRepository
    private let disposeBag = DisposeBag()

    init(repository: SyncRepository) {
        self.repository = repository
    }

    func transform(input: Input) -> Output {
        let state = BehaviorSubject<[String]>(value: [])

        let movies = input.fetchMovies
            .withLatestFrom(state)
            .share(replay: 1)

        movies
            .map { $0.count }
            .flatMap { [repository] in repository.fetchNames(for: $0) }
            .withLatestFrom(state) { $1 + $0 }
            .bind(to: state)
            .disposed(by: disposeBag)

        return Output(
            movieSection: Observable.merge(
                movies.map { $0.count == 0 ? .loading : .loadRemaining },
                state.map(MoviesViewModelState.loaded)
            ).asDriverOnErrorJustComplete()
        )
    }
}

One of the interesting things to note is that the BahviorSubject does not need to be a property of the class. The bind will capture the subject for you.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.