Data not display in collectionView DiffableDataSource MVVM RxSwift

970 views Asked by At

I'm learning MVVM and RxSwift, and I want to display data from GitHub api and populate into collectionViewDiffableDataSource. But it's not displaying my data, even with my snapshot is already setup to accept my data. This is my code

class FollowersListViewModel {
    
    let searchText      = BehaviorRelay<String>(value: "")
    let page            = BehaviorRelay<Int>(value: 1)
    var followers       = BehaviorRelay<[FollowerViewModel]>(value: [])
    var filterFollowers = BehaviorRelay<[FollowerViewModel]>(value: [])
    let hasMoreFollower = BehaviorRelay<Bool>(value: false)
    let isLoading       = BehaviorRelay<Bool>(value: true)
    
    private let manager: NetworkManager
        
    let disposeBag      = DisposeBag()
    
    init(manager: NetworkManager) {
        self.manager = manager
    }
    
    func fetchFollowers(with username: String) {
        isLoading.accept(true)
        searchText.asObservable()
            .filter { $0.count > 2 }
            .throttle(.seconds(3), scheduler: MainScheduler.instance)
            .distinctUntilChanged()
            .flatMapLatest { query in
                self.manager.getFollowers(with: query, page: self.page.value)
            }.subscribe { followers in
                self.isLoading.accept(false)
                self.followers.accept(followers.map { FollowerViewModel(follower: $0)})
                print(self.followers.value)
            } onError: { error in
                print(error)
            }.disposed(by: disposeBag)
    }
    
}

class FollowersListVC: UIViewController {
    
    var viewModel: FollowersListViewModel
    
    enum Section { case main }
    
    var collectionView: UICollectionView!
    var dataSource: UICollectionViewDiffableDataSource<Section, FollowerViewModel>!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViewController()
        setupSearchController()
        setupCollectionView()
        setupCollectionViewDataSource()
        viewModel.fetchFollowers(with: username)
        setupSnapshot()
    }

    private func setupCollectionViewDataSource() {
        dataSource = UICollectionViewDiffableDataSource<Section, FollowerViewModel>(collectionView: collectionView, cellProvider: { (collectionView, indexPath, follower) -> UICollectionViewCell? in
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FollowersCell.reuseID, for: indexPath) as! FollowersCell
            cell.set(followerVM: follower)
            
            return cell
        })
    }

    private func setupSnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, FollowerViewModel>()
        snapshot.appendSections([.main])
        snapshot.appendItems(viewModel.followers.value)
        
        DispatchQueue.main.async { self.dataSource.apply(snapshot, animatingDifferences: true) }
    }
}

I don't know why but it's like my snapshot is not being called, it's seems different when use in MVVM

1

There are 1 answers

2
Daniel T. On BEST ANSWER

Your setupSnapshot() function is being called before the values have been accepted into followers. I haven't used a NSDiffableDataSourceSnapshot yet, but you likely need to do something like this instead:

func setupSnapshot() {
    viewModel.followers
        .map { (followers) in
            with(NSDiffableDataSourceSnapshot<Section, FollowerViewModel>()) {
                $0.appendSections([.main])
                $0.appendItems(followers)
            }
        }
        .observe(on: MainScheduler.instance)
        .subscribe(onNext: { [dataSource] snapshot in
            dataSource.apply(snapshot, animatingDifferences: true)
        })
        .disposed(by: disposeBag)
}

The above uses this helper function. It's optional but I think the code looks cleaner when using it:

func with<T>(_ value: T, _ fn: (inout T) -> Void) -> T {
    var temp = value
    fn(&temp)
    return temp
}

By the way...

  • BehaviorRelays should never be var, always declare them using let.
  • Excessive use of BehaviorRelays like this is a code smell.
  • the map above should be put in your viewModel rather than here.