Search code examples
iosswiftfirebase-realtime-databaseuicollectionviewdiffabledatasource

Swift Diffable datasource will crash when Firebase .observe() updates values


I am building an app where a user can read articles. Each article is written by an author, so, on the article view, when the user clicks on the author's profile picture it navigates to the Author's profile view.

In the Author Profile view, there is a "Follow" button, and in the same view, there are the statistics of the Author (ex. how many articles he/she wrote, how many followers they have, etc.). Something very close to this:

enter image description here

When the author profile view loads, everything is OK. But as soon as the user touches the "Follow" button, the app crashes with the following error:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Section identifier count does not match data source count. This is most likely due to a hashing issue with the identifiers.'

These are the steps I follow:

  1. User in the Author Profile view
  2. User sees the "Follow" button and the stats showing how many users are following the author
  3. Say the author has 10 followers. The user touches the "Follow" button
  4. Behind the scenes, I go into Firebase, get the author, and add the userID into an array of strings: followers: ["asdm132123", "asdadsa12931", "!123123jsdfjsf"] by using .observeSingleEvent() and setValue()
  5. Then I read the data using the .observe() method of Firebase, to update the button state from "Follow" --> "Following" and increase the followers' value from "10" --> "11"

Edit: Code part:

enum AuthorProfileSection {
    case details
    case stats
    case articles
}

Datasource creation:

func configureDataSource() -> UICollectionViewDiffableDataSource<AuthorProfileSection, AnyHashable> {
 
    let dataSource = UICollectionViewDiffableDataSource<AuthorProfileSection, AnyHashable>(collectionView: collectionView) { (collectionView, indexPath, object) -> UICollectionViewCell? in
 
        
        if let object = object as? Author {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AuthorDetailsCell.reuseIdentifier, for: indexPath) as! AuthorDetailsCell
            cell.configure(with: object)
            return cell
        }
        
        
        if let object = object as? AuthorStatsForAuthorProfile {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AuthorStatCell.reuseIdentifier, for: indexPath) as! AuthorStatCell
            cell.configure(with: object)
            return cell
        }
        
        
        if let object = object as? Article {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AuthorArticleCell.reuseIdentifier, for: indexPath) as! AuthorArticleCell

            cell.configure(with: object)
            return cell
        }
        
        return nil
    }
 
    return dataSource
}

Updating snapshot

fileprivate func updateSnapshot(animated: Bool = false) {
    
    guard let authorUID = authorUID else { return }

    Firebase.Database.database().fetchSelectedAuthorProfileData( authorUID: authorUID, fromArticleUID: self.articleUID) { authorProfileSection in
        
        var snapshot = self.dataSource.snapshot()
        
        // sections
        snapshot.appendSections([.details, .stats, .articles])
        snapshot.appendItems([authorProfileSection.details], toSection: .details)
        snapshot.appendItems(authorProfileSection.stats ?? [], toSection: .stats)
        snapshot.appendItems(authorProfileSection.articles ?? [], toSection: .articles)

        self.dataSource.apply(snapshot, animatingDifferences: animated)
    }
}

And the cell:

class AuthorStatCell: UICollectionViewCell, SelfConfiguringCell {

    typealias someType = AuthorStatsForAuthorProfile

    static var reuseIdentifier = "AuthorStatCell"

    override init(frame: CGRect) {
        super.init(frame: frame)
        buildUI()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }



    func configure(with data: AuthorStatsForAuthorProfile) {
        print(data)
    }

    fileprivate func buildUI() {
        backgroundColor = UIColor(red: 0.20, green: 0.83, blue: 0.60, alpha: 1.00)
        layer.cornerRadius = 15
    }
}

And this is the result (in red the part that changes if I touch the "follow" button):

enter image description here

Here is where the app crashes. Any idea why?


Solution

  • I suspect, the issue is inside updateSnapshot() you are getting the current snapshot and adding 3 new sections to it every time you touch follow button

    var snapshot = self.dataSource.snapshot()
    // sections
    snapshot.appendSections([.details, .stats, .articles])
    

    try this

    var snapshot = NSDiffableDataSourceSnapshot<AuthorProfileSection, AnyHashable>()
    

    Or if you are calling another func like createSnapshot Before updateSnapshot then just try removing the line of appendSections

    
    var snapshot = self.dataSource.snapshot()
    // sections
    // remove it
    //snapshot.appendSections([.details, .stats, .articles])
    
    snapshot.appendItems([authorProfileSection.details], toSection: .details)
    snapshot.appendItems(authorProfileSection.stats ?? [], toSection: .stats)
    snapshot.appendItems(authorProfileSection.articles ?? [], toSection: .articles)
    
    self.dataSource.apply(snapshot, animatingDifferences: animated)
    
    

    I’m not sure 100% 🤔 about it bcs I just on my phone rn