diff --git a/GithubRepo.xcodeproj/project.pbxproj b/GithubRepo.xcodeproj/project.pbxproj index 3db703a..99ecbf0 100644 --- a/GithubRepo.xcodeproj/project.pbxproj +++ b/GithubRepo.xcodeproj/project.pbxproj @@ -43,13 +43,11 @@ 0DAFDE3326E71433009FF757 /* SearchViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0DAFDE3126E71433009FF757 /* SearchViewController.xib */; }; 0DB25D6C271A420C00B2968C /* GithubFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D6B271A420C00B2968C /* GithubFetcher.swift */; }; 0DB25D6E271A43E500B2968C /* RemoteGithubFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D6D271A43E500B2968C /* RemoteGithubFetcher.swift */; }; - 0DB25D70271A47D300B2968C /* MockGithubFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D6F271A47D300B2968C /* MockGithubFetcher.swift */; }; 0DB25D73271A4DF500B2968C /* DataMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D72271A4DF500B2968C /* DataMapper.swift */; }; 0DB25D75271A517700B2968C /* RepositoryResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D74271A517700B2968C /* RepositoryResponse.swift */; }; 0DB25D77271A519600B2968C /* OwnerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D76271A519600B2968C /* OwnerResponse.swift */; }; 0DB25D7A271A5A1700B2968C /* GithubDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D79271A5A1700B2968C /* GithubDataSource.swift */; }; 0DB25D7C271A5A8F00B2968C /* RemoteGithubDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D7B271A5A8F00B2968C /* RemoteGithubDataSource.swift */; }; - 0DB25D7E271A5BDE00B2968C /* LocalGithubDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D7D271A5BDE00B2968C /* LocalGithubDataSource.swift */; }; 0DB25D81271A5F5500B2968C /* GithubRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D80271A5F5500B2968C /* GithubRepository.swift */; }; 0DB25D83271A623A00B2968C /* GithubMainRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB25D82271A623A00B2968C /* GithubMainRepository.swift */; }; 0DC0572B2708F95A00EE6F5C /* UIColors+RandomColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC0572A2708F95A00EE6F5C /* UIColors+RandomColor.swift */; }; @@ -95,13 +93,11 @@ 0DAFDE3126E71433009FF757 /* SearchViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SearchViewController.xib; sourceTree = ""; }; 0DB25D6B271A420C00B2968C /* GithubFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubFetcher.swift; sourceTree = ""; }; 0DB25D6D271A43E500B2968C /* RemoteGithubFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteGithubFetcher.swift; sourceTree = ""; }; - 0DB25D6F271A47D300B2968C /* MockGithubFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGithubFetcher.swift; sourceTree = ""; }; 0DB25D72271A4DF500B2968C /* DataMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataMapper.swift; sourceTree = ""; }; 0DB25D74271A517700B2968C /* RepositoryResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryResponse.swift; sourceTree = ""; }; 0DB25D76271A519600B2968C /* OwnerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwnerResponse.swift; sourceTree = ""; }; 0DB25D79271A5A1700B2968C /* GithubDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubDataSource.swift; sourceTree = ""; }; 0DB25D7B271A5A8F00B2968C /* RemoteGithubDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteGithubDataSource.swift; sourceTree = ""; }; - 0DB25D7D271A5BDE00B2968C /* LocalGithubDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalGithubDataSource.swift; sourceTree = ""; }; 0DB25D80271A5F5500B2968C /* GithubRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubRepository.swift; sourceTree = ""; }; 0DB25D82271A623A00B2968C /* GithubMainRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubMainRepository.swift; sourceTree = ""; }; 0DC0572A2708F95A00EE6F5C /* UIColors+RandomColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColors+RandomColor.swift"; sourceTree = ""; }; @@ -311,7 +307,6 @@ children = ( 0DB25D6B271A420C00B2968C /* GithubFetcher.swift */, 0DB25D6D271A43E500B2968C /* RemoteGithubFetcher.swift */, - 0DB25D6F271A47D300B2968C /* MockGithubFetcher.swift */, ); path = Services; sourceTree = ""; @@ -332,7 +327,6 @@ children = ( 0DB25D79271A5A1700B2968C /* GithubDataSource.swift */, 0DB25D7B271A5A8F00B2968C /* RemoteGithubDataSource.swift */, - 0DB25D7D271A5BDE00B2968C /* LocalGithubDataSource.swift */, ); path = DataSource; sourceTree = ""; @@ -510,14 +504,12 @@ buildActionMask = 2147483647; files = ( 0DB25D73271A4DF500B2968C /* DataMapper.swift in Sources */, - 0DB25D70271A47D300B2968C /* MockGithubFetcher.swift in Sources */, 0D5E5BB8271B98A5009CD366 /* Coordinator.swift in Sources */, 0DAFDE3226E71433009FF757 /* SearchViewController.swift in Sources */, 0D5C740B2707FAAC0052FADF /* Int+ConventIntoAFriendlyKMAbbr.swift in Sources */, 0D9A650E27050C420061CD8F /* Owner.swift in Sources */, 0D69A9ED271E3042004A973A /* SearchCoordinator.swift in Sources */, 0DB25D77271A519600B2968C /* OwnerResponse.swift in Sources */, - 0DB25D7E271A5BDE00B2968C /* LocalGithubDataSource.swift in Sources */, 0DB25D6E271A43E500B2968C /* RemoteGithubFetcher.swift in Sources */, 0D9A6516270529090061CD8F /* RepositoryTableViewCell.swift in Sources */, 0DAFDE0D26E6EFC3009FF757 /* AppDelegate.swift in Sources */, diff --git a/GithubRepo.xcodeproj/xcuserdata/alvesmarcos.xcuserdatad/xcschemes/xcschememanagement.plist b/GithubRepo.xcodeproj/xcuserdata/alvesmarcos.xcuserdatad/xcschemes/xcschememanagement.plist index 36efa3e..93034f3 100644 --- a/GithubRepo.xcodeproj/xcuserdata/alvesmarcos.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/GithubRepo.xcodeproj/xcuserdata/alvesmarcos.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ GithubRepo.xcscheme_^#shared#^_ orderHint - 4 + 8 diff --git a/GithubRepo/Application/AppCoordinator.swift b/GithubRepo/Application/AppCoordinator.swift index b6d1b7d..fe80ee8 100644 --- a/GithubRepo/Application/AppCoordinator.swift +++ b/GithubRepo/Application/AppCoordinator.swift @@ -10,7 +10,7 @@ import UIKit class AppCoordinator: Coordinator { var isCompleted: (() -> Void)? - + var childCoordinators = [Coordinator]() private let window: UIWindow diff --git a/GithubRepo/Data/DataSource/GithubDataSource.swift b/GithubRepo/Data/DataSource/GithubDataSource.swift index 765d119..c8faeb5 100644 --- a/GithubRepo/Data/DataSource/GithubDataSource.swift +++ b/GithubRepo/Data/DataSource/GithubDataSource.swift @@ -6,7 +6,8 @@ // import Foundation +import RxSwift protocol GithubDataSource { - func getRepositories(with query: String, completion: @escaping (Result<[Repository], Error>) -> Void) + func getRepositories(with query: String) -> Single<[Repository]> } diff --git a/GithubRepo/Data/DataSource/LocalGithubDataSource.swift b/GithubRepo/Data/DataSource/LocalGithubDataSource.swift deleted file mode 100644 index 2dfbae7..0000000 --- a/GithubRepo/Data/DataSource/LocalGithubDataSource.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// LocalGithubDataSource.swift -// GithubRepo -// -// Created by Marcos Alves on 15/10/21. -// - -import Foundation - -class MockGithubDataSource: GithubDataSource { - let service: GithubFetcher - - init(service: GithubFetcher = MockGithubFetcher()) { - self.service = service - } - - func getRepositories(with query: String, completion: @escaping (Result<[Repository], Error>) -> Void) { - service.fetchRepositories(with: query) { response in - switch response { - case .success(let searchRepositoriesResponse): - let repositories = searchRepositoriesResponse.items.map { RepositoryResponseMapper.map($0) } - completion(.success(repositories)) - case .failure(let error): - completion(.failure(error)) - } - } - } -} diff --git a/GithubRepo/Data/DataSource/RemoteGithubDataSource.swift b/GithubRepo/Data/DataSource/RemoteGithubDataSource.swift index 9080f6d..9a7364d 100644 --- a/GithubRepo/Data/DataSource/RemoteGithubDataSource.swift +++ b/GithubRepo/Data/DataSource/RemoteGithubDataSource.swift @@ -6,6 +6,7 @@ // import Foundation +import RxSwift class RemoteGithubDataSource: GithubDataSource { private let service: GithubFetcher @@ -14,15 +15,10 @@ class RemoteGithubDataSource: GithubDataSource { self.service = service } - func getRepositories(with query: String, completion: @escaping (Result<[Repository], Error>) -> Void) { - service.fetchRepositories(with: query) { response in - switch response { - case .success(let searchRepositoriesResponse): - let repositories = searchRepositoriesResponse.items.map { RepositoryResponseMapper.map($0) } - completion(.success(repositories)) - case .failure(let error): - completion(.failure(error)) + func getRepositories(with query: String) -> Single<[Repository]> { + return self.service.fetchRepositories(with: query) + .map { response in + response.1.items.map { RepositoryResponseMapper.map($0) } } - } } } diff --git a/GithubRepo/Data/Repository/GithubMainRepository.swift b/GithubRepo/Data/Repository/GithubMainRepository.swift index 18a5996..e3c4805 100644 --- a/GithubRepo/Data/Repository/GithubMainRepository.swift +++ b/GithubRepo/Data/Repository/GithubMainRepository.swift @@ -6,48 +6,42 @@ // import Foundation +import RxRelay +import RxSwift class GithubMainRepository: GithubRepository { private let dataSource: GithubDataSource - private(set) var repositories: [Repository] - private(set) var loading: Bool - private(set) var error: Bool - weak var delegate: GithubRepositoryDelegate? + private let disposeBag = DisposeBag() + + let repositories: BehaviorRelay<[Repository]> + let state: BehaviorRelay init(dataSource: GithubDataSource = RemoteGithubDataSource()) { self.dataSource = dataSource - self.repositories = [] - self.loading = false - self.error = false + self.repositories = BehaviorRelay<[Repository]>(value: []) + self.state = BehaviorRelay(value: .inital) } func handleChangeRepositories(_ repo: [Repository]) { - self.repositories = repo - delegate?.didChangeRepositories(repositories: repo) - } - - func handleChangeLoading(_ loading: Bool) { - self.loading = loading - delegate?.didChangeLoading(loading: loading) + self.repositories.accept(repo) } - func handleChangeError(_ error: Bool) { - self.error = error - delegate?.didChangeError(error: error) + func handleChangeState(_ state: FetchState) { + self.state.accept(state) } func fetchRepositories(with query: String) { - handleChangeLoading(true) - self.dataSource.getRepositories(with: query) { [weak self] response in - switch response { - case .success(let repositoriesResponse): - self?.handleChangeRepositories(repositoriesResponse) - self?.handleChangeLoading(false) - self?.handleChangeError(false) - case .failure: - self?.handleChangeLoading(false) - self?.handleChangeError(true) - } - } + self.handleChangeState(.loading) + self.dataSource.getRepositories(with: query) + .subscribe( + onSuccess: { [weak self] in + self?.handleChangeRepositories($0) + self?.handleChangeState($0.isEmpty ? .empty: .content) + }, + onFailure: { [weak self] _ in + self?.handleChangeState(.error) + } + ) + .disposed(by: disposeBag) } } diff --git a/GithubRepo/Data/Repository/GithubRepository.swift b/GithubRepo/Data/Repository/GithubRepository.swift index b3a95c3..ddb16b4 100644 --- a/GithubRepo/Data/Repository/GithubRepository.swift +++ b/GithubRepo/Data/Repository/GithubRepository.swift @@ -6,18 +6,15 @@ // import Foundation +import RxRelay -protocol GithubRepositoryDelegate: AnyObject { - func didChangeLoading(loading: Bool) - func didChangeRepositories(repositories: [Repository]) - func didChangeError(error: Bool) +enum FetchState { + case loading, error, content, empty, inital } protocol GithubRepository { - var delegate: GithubRepositoryDelegate? { get set } - var repositories: [Repository] { get } - var loading: Bool { get } - var error: Bool { get } + var repositories: BehaviorRelay<[Repository]> { get } + var state: BehaviorRelay { get } func fetchRepositories(with query: String) } diff --git a/GithubRepo/Data/Services/GithubFetcher.swift b/GithubRepo/Data/Services/GithubFetcher.swift index fd746d2..c8bb12a 100644 --- a/GithubRepo/Data/Services/GithubFetcher.swift +++ b/GithubRepo/Data/Services/GithubFetcher.swift @@ -6,7 +6,8 @@ // import Foundation +import RxSwift protocol GithubFetcher { - func fetchRepositories(with query: String, completion: @escaping(Result) -> Void) + func fetchRepositories(with query: String) -> Single<(HTTPURLResponse, SearchRepoResponse)> } diff --git a/GithubRepo/Data/Services/MockGithubFetcher.swift b/GithubRepo/Data/Services/MockGithubFetcher.swift deleted file mode 100644 index 7d93fdf..0000000 --- a/GithubRepo/Data/Services/MockGithubFetcher.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// LocalGithubFetcher.swift -// GithubRepo -// -// Created by Marcos Alves on 15/10/21. -// - -import Foundation - -struct MockGithubFetcher: GithubFetcher { - func fetchRepositories(with query: String, completion: @escaping (Result) -> Void) { - let searchResponseMock = SearchRepoResponse( - totalCount: 218_807, incompleteResults: false, items: [ - RepositoryResponse( - id: 44_838_949, - name: "swift", - description: "The Swift Programming Language", - language: "C++", - forks: 9_202, - stars: 57_387, - owner: OwnerResponse( - id: 10_639_145, - login: "apple", - avatarUrl: "https://avatars.githubusercontent.com/u/10639145?v=4" - ) - ), - RepositoryResponse( - id: 94_066_125, - name: "Swift", - description: "🥇Swift基础知识大全,🚀Swift学习从简单到复杂,不断地完善与更新, 欢迎Star❤️,欢迎Fork", - language: "C", - forks: 417, - stars: 1_464, - owner: OwnerResponse( - id: 27_724_501, - login: "iOS-Swift-Developers", - avatarUrl: "https://avatars.githubusercontent.com/u/27724501?v=4" - ) - ) - ] - ) - completion(.success(searchResponseMock)) - } -} diff --git a/GithubRepo/Data/Services/RemoteGithubFetcher.swift b/GithubRepo/Data/Services/RemoteGithubFetcher.swift index 0c48ced..b03e60a 100644 --- a/GithubRepo/Data/Services/RemoteGithubFetcher.swift +++ b/GithubRepo/Data/Services/RemoteGithubFetcher.swift @@ -7,25 +7,18 @@ import Alamofire import Foundation +import RxAlamofire +import RxSwift struct GithubFetcherConstants { static let kUrl = "https://api.github.com/search/repositories" } struct RemoteGithubFetcher: GithubFetcher { - func fetchRepositories(with query: String, completion: @escaping(Result) -> Void) { + func fetchRepositories(with query: String) -> Single<(HTTPURLResponse, SearchRepoResponse)> { guard let url = URL(string: GithubFetcherConstants.kUrl) else { - completion(.failure(URLError(.badURL))) - return + return Single.error(URLError(.badURL)) } - AF.request(url, method: .get, parameters: ["q": query]) - .responseDecodable(of: SearchRepoResponse.self) { response in - switch response.result { - case .success(let data): - completion(.success(data)) - case .failure(let error): - completion(.failure(error)) - } - } + return RxAlamofire.requestDecodable(.get, url, parameters: ["q": query]).asSingle() } } diff --git a/GithubRepo/Scenes/About/AboutCoordinator.swift b/GithubRepo/Scenes/About/AboutCoordinator.swift index 7079348..7f28d1d 100644 --- a/GithubRepo/Scenes/About/AboutCoordinator.swift +++ b/GithubRepo/Scenes/About/AboutCoordinator.swift @@ -10,7 +10,7 @@ import UIKit class AboutCoordinator: NavigationCoordinator { var isCompleted: (() -> Void)? - + var rootViewController: UINavigationController var childCoordinators = [Coordinator]() diff --git a/GithubRepo/Scenes/Search/SearchCoordinator.swift b/GithubRepo/Scenes/Search/SearchCoordinator.swift index aedd556..459bfaf 100644 --- a/GithubRepo/Scenes/Search/SearchCoordinator.swift +++ b/GithubRepo/Scenes/Search/SearchCoordinator.swift @@ -10,7 +10,7 @@ import UIKit class SearchCoordinator: NavigationCoordinator { var isCompleted: (() -> Void)? - + var rootViewController: UINavigationController var childCoordinators = [Coordinator]() diff --git a/GithubRepo/Scenes/Search/View/SearchViewController.swift b/GithubRepo/Scenes/Search/View/SearchViewController.swift index 0ca1496..5647f6f 100644 --- a/GithubRepo/Scenes/Search/View/SearchViewController.swift +++ b/GithubRepo/Scenes/Search/View/SearchViewController.swift @@ -5,6 +5,9 @@ // Created by Marcos Alves on 07/09/21. // +import RxCocoa +import RxRelay +import RxSwift import UIKit class SearchViewController: UIViewController { @@ -18,6 +21,7 @@ class SearchViewController: UIViewController { // MARK: - Attributes + private let disposeBag = DisposeBag() private var searchViewModel: SearchViewModel? private var searchTimer: Timer? @@ -48,6 +52,8 @@ class SearchViewController: UIViewController { super.viewDidLoad() registerTableViewCell() + subscribeSearchState() + subscribeTableData() prepareUI() } @@ -55,7 +61,6 @@ class SearchViewController: UIViewController { func bindViewModel(to viewModel: SearchViewModel) { self.searchViewModel = viewModel - self.searchViewModel?.delegate = self } // MARK: - Setup @@ -71,8 +76,6 @@ class SearchViewController: UIViewController { } private func registerTableViewCell() { - tableView?.delegate = self - tableView?.dataSource = self tableView?.register( UINib( nibName: RepositoryTableViewCell.kTableViewCellIdentifier, @@ -85,74 +88,72 @@ class SearchViewController: UIViewController { // MARK: - Helper Methods private func handleSearchInitialState() { + self.spinner.stopAnimating() self.stateView?.isHidden = false self.stateTextView?.text = kInitialSearchStateText self.stateImageView?.image = UIImage(named: "Bookmark") } private func handleSearchEmptyState() { + self.spinner.stopAnimating() self.stateView?.isHidden = false self.stateTextView?.text = kEmptySearchStateText self.stateImageView?.image = UIImage(named: "BookmarkMad") } private func handleSearchErrorState() { + self.spinner.stopAnimating() self.stateView?.isHidden = false self.stateTextView?.text = kErrorSearchStateText self.stateImageView?.image = UIImage(named: "BookmarkError") } -} - -// MARK: - Notifications from View Model -extension SearchViewController: SearchViewModelDelegate { - func onChangeSearchError(error: Bool) { - DispatchQueue.main.async { - if error { - self.handleSearchErrorState() - } - } - } - - func onChangeSearchLoadingState(isLoading: Bool) { - DispatchQueue.main.async { - if isLoading { - self.spinner.startAnimating() - } else { - self.spinner.stopAnimating() - } - } + private func handleSearchLoadingState() { + self.spinner.startAnimating() + self.stateView?.isHidden = true } - func onChangeSearchRepository(repoCellViewModels: [RepositoryCellViewModel]) { - DispatchQueue.main.async { - if repoCellViewModels.isEmpty { - self.handleSearchEmptyState() - } else { - self.stateView?.isHidden = true - self.tableView?.reloadData() - } - } + private func handleSearchContentState() { + self.spinner.stopAnimating() + self.stateView?.isHidden = true } } -// MARK: - Table View Extension - -extension SearchViewController: UITableViewDataSource, UITableViewDelegate { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchViewModel?.repositoryCellViewModels.count ?? 0 +// MARK: - Handle Notifications from View Model + +extension SearchViewController { + func subscribeSearchState() { + searchViewModel?.state + .asDriver() + .drive { [weak self] value in + switch value { + case .inital: + self?.handleSearchInitialState() + case .loading: + self?.handleSearchLoadingState() + case .error: + self?.handleSearchErrorState() + case .empty: + self?.handleSearchEmptyState() + case .content: + self?.handleSearchContentState() + } + } + .disposed(by: disposeBag) } - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell( - withIdentifier: RepositoryTableViewCell.kTableViewCellIdentifier - ) as? RepositoryTableViewCell, indexPath.row < self.searchViewModel?.repositoryCellViewModels.count ?? 0 else { - return RepositoryTableViewCell() + func subscribeTableData() { + guard let tableView = self.tableView else { + return } - if let repositoryViewModel = self.searchViewModel?.repositoryCellViewModels[indexPath.row] { - cell.setupCell(with: repositoryViewModel) + searchViewModel?.repositoryCellViewModels.bind( + to: tableView.rx.items( + cellIdentifier: RepositoryTableViewCell.kTableViewCellIdentifier, + cellType: RepositoryTableViewCell.self + )) { _, item, cell in + cell.setupCell(with: item) } - return cell + .disposed(by: disposeBag) } } diff --git a/GithubRepo/Scenes/Search/ViewModel/SearchViewModel.swift b/GithubRepo/Scenes/Search/ViewModel/SearchViewModel.swift index 127961e..5c9e767 100644 --- a/GithubRepo/Scenes/Search/ViewModel/SearchViewModel.swift +++ b/GithubRepo/Scenes/Search/ViewModel/SearchViewModel.swift @@ -6,26 +6,27 @@ // import Foundation +import RxRelay +import RxSwift class SearchViewModel: ViewModelSearching { // MARK: - Attributes + private let disposeBag = DisposeBag() private var githubRepository: GithubRepository - private(set) var repositoryCellViewModels: [RepositoryCellViewModel] - private(set) var loading: Bool - private(set) var error: Bool + private(set) var repositoryCellViewModels: BehaviorRelay<[RepositoryCellViewModel]> + private(set) var state: BehaviorRelay private weak var coordinator: SearchCoordinator? - weak var delegate: SearchViewModelDelegate? // MARK: - Constructors init(coordinator: SearchCoordinator, repository: GithubRepository = GithubMainRepository()) { self.coordinator = coordinator - self.repositoryCellViewModels = [] - self.loading = false - self.error = false + self.repositoryCellViewModels = BehaviorRelay(value: []) + self.state = BehaviorRelay(value: .inital) self.githubRepository = repository - self.githubRepository.delegate = self + + self.bindRepository() } // MARK: - Methods @@ -33,23 +34,16 @@ class SearchViewModel: ViewModelSearching { func fetchRepositories(query: String) { githubRepository.fetchRepositories(with: query) } -} - -// MARK: - Notifications from Repository - -extension SearchViewModel: GithubRepositoryDelegate { - func didChangeLoading(loading: Bool) { - self.loading = loading - self.delegate?.onChangeSearchLoadingState(isLoading: loading) - } - - func didChangeRepositories(repositories: [Repository]) { - self.repositoryCellViewModels = repositories.map { RepositoryCellViewModel(repository: $0) } - self.delegate?.onChangeSearchRepository(repoCellViewModels: self.repositoryCellViewModels) - } - func didChangeError(error: Bool) { - self.error = error - self.delegate?.onChangeSearchError(error: error) + private func bindRepository() { + githubRepository.state + .bind(to: self.state) + .disposed(by: disposeBag) + githubRepository.repositories + .map({ repos in + repos.map { RepositoryCellViewModel(repository: $0) } + }) + .bind(to: self.repositoryCellViewModels) + .disposed(by: disposeBag) } } diff --git a/GithubRepo/Scenes/Search/ViewModel/ViewModel.swift b/GithubRepo/Scenes/Search/ViewModel/ViewModel.swift index 9c1746c..ed5c89c 100644 --- a/GithubRepo/Scenes/Search/ViewModel/ViewModel.swift +++ b/GithubRepo/Scenes/Search/ViewModel/ViewModel.swift @@ -7,14 +7,6 @@ import Foundation -protocol SearchViewModelDelegate: AnyObject { - func onChangeSearchLoadingState(isLoading: Bool) - func onChangeSearchRepository(repoCellViewModels: [RepositoryCellViewModel]) - func onChangeSearchError(error: Bool) -} - protocol ViewModelSearching { - var delegate: SearchViewModelDelegate? { get set } - func fetchRepositories(query: String) } diff --git a/Podfile b/Podfile index f33d5e9..02e9ed1 100644 --- a/Podfile +++ b/Podfile @@ -9,4 +9,7 @@ target 'GithubRepo' do pod 'Alamofire' pod 'Kingfisher' pod 'SwiftLint' + pod 'RxSwift' + pod 'RxCocoa' + pod 'RxAlamofire' end diff --git a/Podfile.lock b/Podfile.lock index b9655dc..b4cd567 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,24 +1,46 @@ PODS: - Alamofire (5.4.3) - Kingfisher (6.3.1) + - RxAlamofire (6.1.1): + - RxAlamofire/Core (= 6.1.1) + - RxAlamofire/Core (6.1.1): + - Alamofire (~> 5.4) + - RxSwift (~> 6.0) + - RxCocoa (6.2.0): + - RxRelay (= 6.2.0) + - RxSwift (= 6.2.0) + - RxRelay (6.2.0): + - RxSwift (= 6.2.0) + - RxSwift (6.2.0) - SwiftLint (0.44.0) DEPENDENCIES: - Alamofire - Kingfisher + - RxAlamofire + - RxCocoa + - RxSwift - SwiftLint SPEC REPOS: trunk: - Alamofire - Kingfisher + - RxAlamofire + - RxCocoa + - RxRelay + - RxSwift - SwiftLint SPEC CHECKSUMS: Alamofire: e447a2774a40c996748296fa2c55112fdbbc42f9 Kingfisher: 016c8b653a35add51dd34a3aba36b580041acc74 + RxAlamofire: beb75a1c452d0de225651db4903f5d29d034a620 + RxCocoa: 4baf94bb35f2c0ab31bc0cb9f1900155f646ba42 + RxRelay: e72dbfd157807478401ef1982e1c61c945c94b2f + RxSwift: d356ab7bee873611322f134c5f9ef379fa183d8f SwiftLint: e96c0a8c770c7ebbc4d36c55baf9096bb65c4584 -PODFILE CHECKSUM: 505c925431ff309d40347527ff94565ab266bf2c +PODFILE CHECKSUM: a7941cc001e74f124a43f0a2fa494b25445e7d9b COCOAPODS: 1.10.1