func grabPosts2(){
posts = []
currentUserRef?.child("invitedToPosts").queryOrdered(byChild: "timestamp").queryStarting(atValue: cutoff).observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
let postKey = snap.key
DataService.ds.REF_INITIATIONS.child(postKey).observeSingleEvent(of: .value, with: { (snapshot: FIRDataSnapshot) in
if let dict = snapshot.value as? Dictionary<String,Any>{
let post = Post(postKey: snapshot.key, postData: dict)
self.posts.append(post)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
}
})
}
Database example:
User
invitedToPosts
09283094823904
timestamp: 30909000909
Initiations
09283094823904
post information
What the code above is doing is going into a user's invitedToPosts, if the timestamp is recent enough, grab the key and go find it in the Initiations, then grab out the post information, make a post object with it, append it to my posts array, and reload my tableview each time.
I run this code on startup and then I've got both a button and a refresh controller that runs this function.
If I slowly tap on the refresh button or do my drag to refresh slowly I never get a crash with this code. However, if I tap on the button super quickly, sometimes I can tap it 3 times and sometimes I can tap on it 20 times, but eventually the code crashes.
I'm getting an index out of range on this line in my cellForRowAtIndexPath
let post = posts[indexPath.row]
So I assume it's got something to do with clearing out the post array and trying to refresh the page while another request is running slowly and then it catches up and everything goes nuts. No idea though.
Any ideas how to safely run this code or how to successfully write this so that when I aim to refresh my tableview with up to date information I won't get this crash?
Thanks!
Related
I use the pagination method at the bottom of this question which works fine. Once the startKey is initialized with a key from the db that's the point at which the next pagination will occur from and the next set of posts (children) will get appended to the datasource.
I realized that if that key got deleted by the initial user who posted it, then once I try to paginate from that key since it doesn't exist the children that would get appended if it was there wouldn't get appended because they wouldn't be accessible (they're accessible based on that key).
The only thing I could think of was to first check if the key exists() and if it doesn't just start everything over from the beginning:
if !snapshot.exists() {
self?.startKey = nil
self?.datasource.removeAll()
self?.collectionView.reloadData()
self?.handlePagination()
return
}
It works fine but it's not the most fluid user experience because I'd rather just pull the posts prior to the deleted key (I have no prior reference to them).
A possibility is to just keep an array of all the previous keys and just loop through them but there's always a minute chance that those keys can get deleted by the users who posted them also.
Any ideas of how to get around this?
var startKey: String?
func handlePagination() {
if startKey == nil {
Database...Ref.child("posts")
.queryOrderedByKey()
.queryLimited(toLast: 7)
.observeSingleEvent(of: .value, with: { [weak self](snapshot) in
guard let children = snapshot.children.allObjects.first as? DataSnapshot else { return}
for child in snapshot.children.allObjects as! [DataSnapshot] {
// append child to datasource
}
self?.startKey = children.key
})
} else {
Database...Ref.child("posts")
.queryOrderedByKey()
.queryEnding(atValue: startKey!)
.queryLimited(toLast: 8)
.observeSingleEvent(of: .value, with: { [weak self](snapshot) in
if !snapshot.exists() {
self?.startKey = nil
self?.datasource.removeAll()
self?.collectionView.reloadData()
self?.handlePagination()
return
}
guard let children = snapshot.children.allObjects.first as? DataSnapshot else { return}
for child in snapshot.children.allObjects as! [DataSnapshot] {
// insert child in datasource at startIndex
}
self?.startKey = children.key
})
}
}
there's always a minute chance that those keys can get deleted by the users who posted them also
You're saying that you could keep the keys of the current page, and just loop through until you find an item that was not deleted. In the case that all have been deleted, you'd reach the end of the list of keys, and should create a new query that doesn't have a startAt(). This will give you the first X items, which is the correct behavior in that case I think.
In general though: dealing with realtime and pagination is really hard, which is the main reason the paging adapters in FirebaseUI don't do realtime updates.
I am trying to show my last sent message on my home screen of the chat messenger, from storyboard it looks like :
and the code I used are:
func getAllMsg() {
self.users = []
let fromId = Auth.auth().currentUser!.uid
let ref = Database.database().reference().child("privateMessages").child(fromId)
ref.observeSingleEvent(of: .value) { (snapshot) in
for snap in snapshot.children.allObjects as! [DataSnapshot] {
// retrieving receiver's ID
let chatUserID = snap.key
let ref2 = Database.database().reference().child("users").child(chatUserID)
// to retrieve message ID
ref2.observeSingleEvent(of: .value, with: { (snapshot2) in
let newUser = User(dictionary: snapshot2.value as! [String: AnyObject])
// to get the last message
ref.child(chatUserID).queryLimited(toLast: 1).observeSingleEvent(of: .value, with: { (snapshot3) in
let value = snapshot3.children
while let rest = value.nextObject() as? DataSnapshot {
newUser.lastMessage = (rest.value as! [String: AnyObject])["textMessages"] as? String
self.users.append(newUser)
DispatchQueue.main.async {
self.tableView.reloadData()
}
break
}
})
})
}
}
}
I have done some changes to my database and the above codes works, but after i changed the way i do my database, it doesnt work anymore
previously, my firebase looks like
now my firebase looks like:
i made a chatRoomId by using send and receiver ID. however I am now not able to show my last sent message which was supposed to show on my homescreen. Any ways I can go about this? Or the way I fetched my database is wrong?
Your query is wrong according to your db structure. Also don't perform lastMessage query inside the block of other query because this is totally independent query and not related with any. Below piece of code will work for you.
let ref = kFirDefaultDatabase.reference().child("yourChatRoomId").queryOrdered(byChild: "fromId").queryEqual(toValue: "0YfqnPIOYFYKb8cYZMHnSYti62i2").queryLimited(toLast: 1)
ref.observeSingleEvent(of: DataEventType.value) { (snapshot) in
if snapshot.exists() {
print(snapshot.value)
}
}
This will fetch the last message sent by fromId for the requested chatRoomId. For more detail have a look at Firebase Queries doc.
And if you want to do this in table for all users like in WhatsApp or other chatting application then you will need to make an extra table LastMessages and save last message information here corresponding to each chatRoomId or if possible save this detail somewhere you can fetch with the tableData so that you don't need to query for each chatRoom in a loop.
You can do some better stuff to make it faster. Use CoreData or Sqlite and save/update lastMessage information into local db whenever you send or received any message, where chatRoomId will be a primary key and first get the information from local db and show in the table immediately and mean while you can fetch the data from server and update your local db and refresh the table.
EDIT: For comment to get the last message regardless I send to recipient or recipient send to me.
Remove orderBy query and just use limitToLast. See below code:
let ref = kFirDefaultDatabase.reference().child("yourChatRoomId").queryLimited(toLast: 1)
ref.observeSingleEvent(of: DataEventType.value) { (snapshot) in
if snapshot.exists() {
print(snapshot.value)
}
}
You need to set up rooms differently.
If you you have 3 people in a room, what will the RoomID be ? If someone leaves the room how will you know what the room history is ?
I have a weird error that I do not know how to solve, let me explain:
(I should say there is more code before the following, if you want to see)
for i in 0..<self.followings.count {
let following = self.followings[i]
print("\(following.Id) and \(self.followings.count)")
FIRDatabase.database().reference().child("feed-items").queryOrderedByChild("profilePhoto").queryEqualToValue(following.Id).observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
var newUpdates = [Sweet]()
for update in snapshot.children {
let updateObject = Sweet(snapshot: update as! FIRDataSnapshot)
newUpdates.append(updateObject)
}
self.updates = newUpdates.reverse()
print(self.updates.count)
self.tableView.reloadData()
}) { (error: NSError) in
print(error.description)
}
}
This is my code for showing posts from users that currentUser is following, in my feed (UITableView). The problem is when someone is clicking the like button, the person which post was liked will double every time currentUser clicks like.
I can prevent that from happening if I mode this line of code: var newUpdates = [Sweet]() up before viewDidLoad. But if I do so then the feed will only show posts from one of the users that currentUser is following - even if I am following two users or more.
So how can I show all posts from users I am following and NOT duplicate when clicking like on one of their posts? Let me know if you need more code :-)
I cannot seem to figure out why my data keeps duplicating after a new post has been made. When the app starts everything looks as it should and the data is presented properly from firebase.
After I make a new post and return to my feed the new data appears but the old data has been duplicated.
Here is my code,
override func viewDidAppear(_ animated: Bool) {
ref = FIRDatabase.database().reference()
ref.child("brackets").observe(FIRDataEventType.value, with: { (snapshot) in
if let bracketsSnapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for brackets in bracketsSnapshot {
if let bracketsDict = brackets.value as? Dictionary <String, Any> {
let key = brackets.key
let post = BracketsPublicFeed(postKey: key, postData: bracketsDict)
self.posts.append(post)
}
}
}
self.collectionView.reloadData()
})
}
Here is the code used when posted,
func PostData(itemToCell1: String, itemToCell2: String, itemToCell3: String, itemToCell4: String, userName: String?/*, userID: String?*/) {
let ref : FIRDatabaseReference!
ref = FIRDatabase.database().reference()
let votes = 0
let userName = ""
let key = ref.child("brackets").childByAutoId().key
let post = [
"userID": userID as AnyObject,
"item1": itemToCell1 as AnyObject,
"item2": itemToCell2 as AnyObject,
"item3": itemToCell3 as AnyObject,
"item4": itemToCell4 as AnyObject,
"userName": userName as AnyObject,
"votes": votes as AnyObject ]
let childUpdates = ["/brackets/\(key)": post,
"/user-posts/\(userID)/\(key)/": post]
ref.updateChildValues(childUpdates)
}
I have tried various things such as,
ref.removeAllObservers()
self.posts.removeAll()
in the viewDidDisappear
I will gladly post more code if I am not painting the whole picture.
UPDATE,
Thanks for your responses guys. So far none of them have worked except for the suggestion to use ref.child("brackets").removeAllObservers()
The thing is though it that it works exactly as it should when I write this code,
self.posts.removeAll()
ref.child("brackets").removeAllObservers()
So I get rid of everything and reload it every time the view appears.
From Firebase docs:
- (void) removeAllObservers
Removes all observers at the current reference, but does not remove any observers at child references.
removeAllObservers must be called again for each child reference where a listener was established to remove the observers.
So, ref.removeAllObservers() in viewDidDisappear won't remove observer for ref.child("brackets").
Try this in viewDidDisappear:
ref.child("brackets").removeAllObservers()
did you try observerSingleEvent? Seems like you're keep adding new observer every time view appears. There is, actually, neat way to use FirebaseCollectionViewDataSource without populating data manually, like this:
dataSource = FirebaseCollectionViewDataSource.init(query: ref.child("brackets"),
modelClass: Post.self,
nibNamed: "PostViewCell",
cellReuseIdentifier: "lesson",
view: self.collectionView)
You're listening for a .Value event, which triggers with the entire list each time that the data changes (such as when an item is added). So the first time it triggers with e.g. items 1, 2 and 3. Then next time it triggers with items 1, 2, 3 and 4.
You can deal with this in two ways:
clear the collection view every time
respond to .ChildXxx events instead
The first is simplest, but will lead to a flickering UI. So I'll show the second option: responding to .ChildAdded.
A .ChildAdded event is fired for every existing child when you first attach the observer. So that's items 1, 2 and 3. When you then add a new item, it will fire with only item 4. That allows you to simply add the new item to the collection view:
ref.child("brackets").observe(FIRDataEventType.ChildAdded, with: { (snapshot) in
if let bracketsDict = snapshot.value as? Dictionary <String, Any> {
let key = brackets.key
let post = BracketsPublicFeed(postKey: key, postData: bracketsDict)
self.posts.append(post)
self.collectionView.reloadData()
})
Note: I didn't compile this code, so you may have to fix some problem with it. If you do, leave comments and I'll update.
So I am building a notes app and have tried everything but can not figure this issue out. I have 3 UIViewController's. When you click a button on the first UIViewController, it shoots you over to the third one which consist of a UITextView, UITextField and a Static UILabel which gets updated with some information. When you fill out these fields and tap the back button it brings you to the second view controller which is a table that gets updated with this information.
The issue is: when I tap the UITableViewCell it loads the information back to the third view controller so the user can edit his notes but when I come back to the UITableView it creates a brand new cell instead of just updating the old one.
If I could just update my array with the same object I sent back to be edited by the user I think this issue would be solved but I have no idea how to do this. Thanks for the help!
VC2 - this is how I am sending my data from the tableView to the textView Back
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextView = segue.destinationViewController as! TextViewController
guard let indexPath = self.tableView.indexPathForSelectedRow else {
print("Didnt work")
return
}
let dataToSendBackToBeEdited = textViewObjectData[indexPath.row]
print(dataToSendBackToBeEdited.dreamText)
print(dataToSendBackToBeEdited.dreamTitle)
print(dataToSendBackToBeEdited.dreamType)
print(dataToSendBackToBeEdited.description)
print(dataToSendBackToBeEdited)
nextView.objectSentFromTableViewCell = dataToSendBackToBeEdited
}
This is how I am saving the information the the user taps back to go to the tableView
func determineSave() {
guard var savedDreamArray = NSKeyedUnarchiver.unarchiveObjectWithFile(TextViewController.pathToArchieve.ArchiveURL.path!) as? [Dream] else {
//First dream object saved
let dreamObject = Dream(dreamTitle: titleForDream.text!, dreamType: typeOfDreamLabel.text!, dreamText: textView.text, currentDate: NSDate())
dreamArray.append(dreamObject)
NSKeyedArchiver.archiveRootObject(dreamArray, toFile: pathToArchieve.ArchiveURL.path!)
return
}
//print(savedDreamArray.count)
//print(savedDreamArray.first!.dreamTitle)
let dreamObject = Dream(dreamTitle: titleForDream.text!, dreamType: typeOfDreamLabel.text!, dreamText: textView.text!, currentDate: NSDate())
savedDreamArray.append(dreamObject)
NSKeyedArchiver.archiveRootObject(savedDreamArray, toFile: pathToArchieve.ArchiveURL.path!)
}
I was having this issue as well. Came in here, and got the answer. The array!
I was appending the array as well, which apparently was causing duplicate cells to appear.
I just reset my array back to empty before I retrieved the data again and reloaded the table.
I'm using Firebase so my code looks like this:
DataService.instance.dateProfileRef.observeEventType(FIRDataEventType.Value) { (snapshot: FIRDataSnapshot)
in
//have to clear the array here first before reloading the data. otherwise you get duplicates
self.posts = [] //added this line
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
let snapUserID = (snap.childSnapshotForPath("userID").value!)
if snapUserID as? String == USER_ID {
if let profileDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = Post(postKey: key, postData: profileDict)
self.posts.append(post)
You do a .append() on your array, this will add a new cell.
You must find the index of the object you want to update and then assign it:
array[index] = newObject