FirebaseTableViewDataSource crashes on user signout and signin - ios

My app has a UITableViewController which uses a FirebaseTableViewDataSource (from FirebaseUI). The table shows the user's bookmarked posts correctly, but when I log that user off, and log another user in, the app crashes with the following message:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'*
I suspect the problem is related to how FirebaseUI updates the content of the tableView. I've being debugging this for the past 4 days; searched the SO questions; read the relevant docs but none mentions this unique issue. Any help will be much appreciated.
The Details (Sorry it's actually long)
This is my database structure:
|
+-posts
| |
| +-$postid
| |
| +-/* properties of a post: title, text, etc */
|
+-users
|
+-$userid
|
+-bookmarks
|
+-post1: true
|
+-post2: true
I am using FirebaseUI to show a user his/her bookmarks by passing the users/$userid/bookmarks ref to FirebaseTableViewDataSource as a query. Then for each key, I observe a single value event on posts/$postid in order to retrieve the post details...code below:
self.authStateListenerHandle = FIRAuth.auth()?.addStateDidChangeListener { auth, user in
guard let user = user else {
return
}
self.query = FIRDatabase.database().reference().child("users").child(user.uid).child("bookmarks")
self.tableViewDataSource = FirebaseTableViewDataSource(query: self.query!, view: self.tableView, populateCell: { (tableView, indexPath, snapshot) -> UITableViewCell in
if snapshot.exists() {
let postRef = FIRDatabase.database().reference().child("posts").child(snapshot.key)
let cell = tableView.dequeueReusableCell(withIdentifier: BookmarksVC.reuseIdentifier, for: indexPath) as! MiniTableCell
postRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
let post = Event(snapshot: snapshot)!
let postVM = EventViewModel(post: post)
cell.populateCellWithEvent(postVM)
cell.delegate = self
}
})
return cell
}
else {
return UITableViewCell()
}
})
self.tableView.dataSource = self.tableViewDataSource
}
I put the above code in viewDidAppear and then remove the authStateListenerHandle in viewWillDisappear like so
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let handle = self.authStateListenerHandle {
FIRAuth.auth()?.removeStateDidChangeListener(handle)
}
}
Almost everything works fine except, when I am viewing the bookmarks for a user, then log out and log back in, the app crashes with the message
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
*

In the viewWillDisappear set the self.tableViewDataSource = nil. So, that you don't update the dataSource improperly.

Related

How to append row in tableview swift?

I'm adding data in my model and model is assigned to tableview to reload data. But every time reloading is not looking good. so I want just last element that was added in model, should be appended in already exist tableview. Tried so many ways but getting crash when my tableview is empty.
let lastSectionIndex = self.isGroupChat ? self.objGroupChatList!.count-1 : self.objSingleChatList!.count-1
var lastRow = 0
if self.isGroupChat {
lastRow = (self.objGroupChatList?[lastSectionIndex].count ?? 1)
} else {
lastRow = (self.objSingleChatList?[lastSectionIndex].count ?? 1)
}
let IndexPathOfLastRow = IndexPath(row: lastRow-1, section: lastSectionIndex)
self.tableView.beginUpdates()
self.tableView.insertRows(at: [IndexPathOfLastRow], with: UITableViewRowAnimation.none)
self.tableView.endUpdates()
This is crashing with error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of sections. The number of sections contained in the table
view after the update (1) must be equal to the number of sections
contained in the table view before the update (0), plus or minus the
number of sections inserted or deleted (0 inserted, 0 deleted).'
You should use insertSections for new sections. insertRows only works for existing sections.
You need to do something like,
let section = 0 //get your section here...
dataSource[section].append("five")
let row = dataSource[section].count - 1
tableView.insertRows(at: [IndexPath(row: row, section: section)], with: .none)
This is just an example of how you can get that working. Fill the gaps as per your code.

Error deleting a section on UICollectionView

I have 3 sections that are populated by an array of arrays. Each section has a header and a button that deletes the current section. When I tap the delete button I get the following Error:
Error:
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 (8) must be equal to the number of items contained in that section before the update (3), plus or minus the number of items inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
This is the code for the delete function
#IBAction func deleteSection(_ sender: UIButton) {
// grab the tag representign the section
let section = sender.tag
// Update data model
all.remove(at: section)
// reload data to reflect section change
// ignored due to delete sections
photosCollection?.performBatchUpdates({
self.photosCollection?.deleteSections(NSIndexSet(index: section) as IndexSet)
}, completion: { (finished) in
if finished {
self.photosCollection?.reloadData()
}
})
}
all is my array of arrays.

Dispatch.main.async invalid number of rows in section

I'm having an issue when I check off my task to quickly, which makes the app crash and gives me error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (7) must be equal to the number of rows contained in that section before the update (9), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Code when radiobutton tapped
func RadioTapped(_ cell: TableViewCell) {
if let indexPath = tableView.indexPath(for: cell) {
// Removes task from coreData
let task = self.tasks[indexPath.row]
self.context.delete(task)
do {
self.tasks = try self.context.fetch(TodayTask.fetchRequest())
(UIApplication.shared.delegate as! AppDelegate).saveContext()
// Animate the removal of task cell
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800),execute: {
self.tableView.deleteRows(at: [indexPath], with: .fade)
})
} catch {
print("Fetching failed")
}
}
}
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of rows in section 0. The number of rows contained in an
existing section after the update (7) must be equal to the number of
rows contained in that section before the update (9), plus or minus
the number of rows inserted or deleted from that section (0 inserted,
1 deleted) and plus or minus the number of rows moved into or out of
that section (0 moved in, 0 moved out).'
This crash means when you are deleting your table row. You are not updating your arrayCount for instance you have defined 8 inside your numberOfRowsInSection method then before you delete the row you will also need to update the rows count based on the rows you want to delete. Otherwise when your deleteRows will called it will also called the numberOfRowsInSection method and there count will mismatch and it will crash.

How to deal with 'Assertion Failure' Collection View crashes in Firebase DatabaseUI in Swift?

I'm building a screen on my app where profiles will be displayed on a collection view. My data is being retrieved from Firebase and for the first time I'm using Firebase DatabaseUI.
I'm building out a query, then creating a FUICollectionViewDataSource, dequeuing my cells, and then plugging that dataSource into the collectionView.
The functionality of the dataSource works fine as long as I'm toggling between online and offline users (which checks the value of a child key on the query), but when I change the query to be from male to women, or women to men, (a new query itself) I'm crashing with an NSInternalInconsistenencyException.
*** 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:
(0x18672d1b8 0x18516455c 0x18672d08c 0x1871e502c 0x18cedc14c 0x18c7b1d4c 0x10020f594 0x1002102c8 0x1001eb3e0 0x101609258 0x101609218 0x10160e280 0x1866da810 0x1866d83fc 0x1866062b8 0x1880ba198 0x18c64d7fc 0x18c648534 0x1000c4200 0x1855e95b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
When I'm building out collection views, I'm used to just running commands like this:
collectionView.dataSource = collectionViewDataSource
self.collectionView.reloadData()
But now that calls like above don't work, and I'm not using UICollectionViewDataSource protocol functions, I'm unsure how to go about creating, running, and displaying new queries which will always have differing number of items per section.
What are the functions that I have to run before processing a new query to not deal with these kinds of crashes?
I've tried deleting all the cells from the collectionView's indexPathsForVisibleItems, and then reloading data... I've thrown around collectionView.reloadData() all over the place and still crashes. Not all the time, but the app will definitely crash sooner or later.
How do I go about starting a brand new query on a collectionView and not have to deal with NSInternalInconsistencyExceptions?
Thanks everyone. I love Firebase and would love to figure this out!
-Reezy
When the VC loads, the getUserData() function is called.
When the query is changed, the resetQuery() function is run from a posted notification.
extension BrowseVC {
func getUserData() {
let isOnline = (segmentedControl.selectedSegmentIndex == 0) ? true : false
generateQuery(isOnline: isOnline)
populateDataSource()
}
func resetQuery() {
removeOldData()
getUserData()
}
func removeOldData() {
let indexPaths = collectionView.indexPathsForVisibleItems
collectionView.deleteItems(at: indexPaths)
// collectionView.reloadData()
}
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
collectionView.reloadData()
// collectionView.performBatchUpdates({
// self.collectionView.dataSource = self.collectionViewDataSource
// }, completion: nil)
// collectionView.reloadData()
}
}
}
The reason you're running into this is the FirebaseUI Database data sources assume responsibility for pushing updates to the collection view itself. This way you get animated updates for free and shouldn't ever have to call reloadData.
When you swap out the old data source for a new one, you need to set the data source's collection view to nil so it stops pushing events to the collection view.

NSInternalInconsistencyException occurs when insertng indexPath into tableView but only when row count gets above a certain threshold

I have two tables in which I am dragging and dropping between them: sourceTableView and targetTableView. They each have their own tableViewController. A parentContainerViewController manages a 'slide out' UI which allows the user to 'longPressGesture' on the sourceTableView cell which causes sourceTableView to slide out exposing the targetTableView below at which time the user can 'drop' the dragged cell into place within the targetTableView. All works well as it should until the targetTableView grows to about 21-27 rows in size. Once we get to this size and if I re-run the project I start to get the NSInternalInconsistencyException. The detail of the error reads: reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (27) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
At first I thought I was getting a race condition issue since the number of rows before the update is "0" and after it is "27". Maybe the tableView wasn't able to load before the user drags in the item and calls the insertIndex method on tableView. But then I noticed that it works fine until we get to a certain length. Race condition wouldn't make sense in this case. Now I am thinking it has something to do with with 'visible cell' loaded vs not loaded. But why would it be '0' rows? I have looked all over for the answer. There are many NSInconsistency but none that seem to answer my specific problem.
This is the function call to tell targetTableViewController to insert an item into its datasource then tells targetTableView to insertRowsAtIndexPath :
dragDropDataSource.tableView(self, insertDataItem: item, atIndexPath: indexPath)
self.beginUpdates()
self.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
self.endUpdates()
Here is the targetTableViewController code implementing the above call:
func tableView(tableView: UITableView, insertDataItem dataItem : AnyObject, atIndexPath indexPath: NSIndexPath) -> Void {
if let di = dataItem as? DataItem {
data.insert(di, atIndex: indexPath.row)
}
data is the array that stores the 'DataItems' which are shown in the tableView rows.
Additionally. The targetTableView is initialized with data via a call in its viewDidLoad() to getMyTopTen()
func getMyTopTen() {
let pwAPI = PowWowAPI()
pwAPI.getMyTopTen(self.boardId!) {
(entries:[Entry])in
for(var i=0; i < entries.count; i++) {
let dataItem = DataItem(indexes: String(i), entry: entries[i])
self.data.append(dataItem)
}
}
dispatch_async(dispatch_get_main_queue()) {
self.targetTableView.reloadData()
}
}
I hope I have explained the problem sufficiently. If not please help me make this question better. I am new to asking questions here.

Resources