FirebaseUI-iOS: 'NSInternalInconsistencyException' crash happening when query is changing for CollectionViewDataSource

Step 2: Describe your environment

  • Objective C or Swift: Swift
  • iOS version: 10.2
  • Firebase SDK version: 3.12.0
  • FirebaseUI version: 1.0.0
  • CocoaPods Version: 1.2.0

Step 3: Describe the problem:

For a little more background, I have received help from Morgan Chen regarding my problem on StackOverflow: here

On my collection view, I have a segmented control that allows users to toggle queries from online to offline users, and vice versa.

Even after clearing out the data source as recommended, the app crashes because of NSInternalInconsistencyExceptions.

I’ve been clearing out my data source like this:

collectionView.dataSource = nil
collectionViewDataSource = nil

I am still getting crashes.

Steps to reproduce:

  1. Create query for users that are online.
  2. Remove old data source and then create new query for users that are offline.
  3. Repeat until crash.

Observed Results:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0.  The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
*** First throw call stack:
(0x1838511b8 0x18228855c 0x18385108c 0x18430902c 0x18a00014c 0x1898d5d4c 0x1002680a8 0x100268ddc 0x100243ef4 0x102435258 0x102435218 0x10243a280 0x1837fe810 0x1837fc3fc 0x18372a2b8 0x1851de198 0x1897717fc 0x18976c534 0x10010a65c 0x18270d5b8)
libc++abi.dylib: terminating with uncaught exception of type NSException

Expected Results:

  • I would like for the app to toggle online and offline users as much as they’d like without an app crash.

Relevant Code:

extension BrowseVC {
func getUserData() {
  let isOnline = (segmentedControl.selectedSegmentIndex == 0) ? true : false
  generateQuery(isOnline: isOnline)
  populateDataSource()
}

func resetQuery() {
  removeOldData()
  getUserData()
}

func removeOldData() {
  collectionView.dataSource = nil
  collectionViewDataSource = nil
}

func generateQuery(isOnline: Bool) {
  if let selectedShowMe = UserDefaults.sharedInstance.string(forKey: Constants.ProfileKeys.ShowMe) {
    let forMen = (selectedShowMe == Constants.ProfileValues.Men) ? true : false
    query = FirebaseDB.sharedInstance.buildQuery(forMen: forMen, isOnline: isOnline)
  } else {
    query = FirebaseDB.sharedInstance.buildQuery(forMen: false, isOnline: isOnline)
  }
}

func populateDataSource() {
  if let query = query {
    collectionViewDataSource = FUICollectionViewDataSource(query: query, view: collectionView, populateCell: { (view, indexPath, snapshot) -> ProfileCVCell in
      if let cell = view.dequeueReusableCell(withReuseIdentifier: Constants.CollectionViewCellIdentifiers.ProfileCVCell, for: indexPath) as? ProfileCVCell {
        if let userDictionary = snapshot.value as? [String: AnyObject] {
          if let user = User(uid: snapshot.key, userDictionary: userDictionary) {
            cell.populate(withUser: user)
          }
        }

        return cell
      }
      return ProfileCVCell()
    })

    collectionView.dataSource = collectionViewDataSource
  }
}
}


Initially getUserData() is being called when the VC is first created, but for every query change, resetQuery() is being called which removes the old data source and runs getUserData() again.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 32 (7 by maintainers)

Most upvoted comments

@morganchen12 Solved. Updated my code to the lates sample practices, does the Job.

Hi,

The way our app is designed we need to change the data source during infinite left / right swipe scenario (Changing the month user is observing), so we can’t disable the user controls. (Which is extremely bad anyway from UX standpoint.) Also disabling for a moment is bad because the time frame when the error will manifest itself will grow when you have more data or slower devices.

I hope this helps someone in the future, I ended up doing this, and don’t get the crashes anymore (so far…, fingers crossed)

//Unbind the old datasource from the tableview if there is already one
self.dataSource?.unbind()

//Set empty datasource to the tableview
self.tableView.dataSource = EmptyDataSource()

//Instruct tableview to reload.
//This causes it to remove any rows there might be lingering around.
self.tableView.reloadData() 

// Assign new datasource to self.dataSource here. Do not start to observe yet.

//This will replace the empty datasource we just set and
//start observing and updating the empty tableView
self.dataSource?.bind(to: self.tableView) 

//Ready. Do not reload tableView past this point, datasource will push changes to it!

One future improvement point would probably be for the data source to clean after itself to reset the table to empty state, but that’s not very important as we can do it ourselves.