Swift Showing last sent message in chat using firebase - ios

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 ?

Related

Firebase -How to handle pagination if the key set to paginate from has been deleted

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.

Using observeSingleEvent/snapshot, how can a line be coded that checks if a user added a child entry to the database within the last 24 hours?

Using if (snapshot.exists()){ is easy but how can you modify this to only include snapshots of database entries that were made in the last 24 hours?
///this is how it would look if you were only checking if the user has a snapshot. What you want is to check if the snapshot was made within 24 hours.
super.viewDidAppear(animated)
let databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
databaseRef.child("users").child(uid).child("Photos").observeSingleEvent(of: .value, with: { (snapshot) in
if (snapshot.exists()){
print ("good") }
else{
self.performSegue(withIdentifier: "toVegas", sender: nil)
}
})
If snapshot method does not work, I was thinking that maybe the way certain websites tell you that you can't post within x minutes can be used in reverse (ie performSegue if they haven't posted in x minutes/hours).
One approach to this is to use ServerValue.timestamp() when creating your child nodes as an extra parameter, once you've done that you can then compare the timestamp of the retrieved snapshot and make the validation of the 24 hours time lapse you are talking about
This is an example function on how you can modify your writing method to the database to include the timestamp
func sendMessage() {
let data = ["UserName": userName,
"message" : message,
"timestamp": ServerValue.timestamp()]
Database.database().reference().child("Photos").setValue(data)
}
then in your observer you can do something like this
databaseRef.child("users")
.child(uid)
.child("Photos")
.observeSingleEvent(of: .value, with: { (snapshot) in
guard let message = snapshot.value as? [String:String] else { return }
let timestamp = message["timestamp"] as! UInt64
//TODO - Look up how to convert that value to a date, once you get that done you can compare that date to a constant date of 24hrs and decide what to do next in your code
if (snapshot.exists()){
print ("good")
}else{
self.performSegue(withIdentifier: "toVegas", sender: nil)
}
})

Best way to structure a friend list in Firebase

I am developing an iOS app using Swift 3 and Firebase.
In this app I am creating a friend list with this structure:
Friends
User_id
Friend_user_id_1
Friend_user_id_2
I want to get a list of friends for a specific user and this is possible but since I only store ids of friends I am missing Name and Email.
My idea is to do a second query for each user in the friend list to get their personal data. The option to this is to store the Name and Email along the friend_id but that feels cumbersome since the friend might change their Name or Email.
Friends
User_id
Friend_user_id_1
Name
Email
Is it ok making a second query to get user details on each loop or is that a performance killer?
Do I have to save the Name and Email in list too?
Update
This is how I solve it right now
func setupContent() {
let user = FIRAuth.auth()?.currentUser
if user != nil {
DataService.dataService.FRIEND_REF.child((user?.uid)!).observe(.value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
DataService.dataService.USER_REF.child(snap.key).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? Dictionary<String, AnyObject>
let friend = Friend(key: snap.key, dictionary: value!)
self.friends.insert(friend, at: 0)
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
}
}
})
}
}
Thankful for all help before I start coding.

Code crashing when ran too frequently. Firebase / Swift

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!

Querying Firebase using Swift 3 and Xcode 8

We are trying to read specific data sets from Firebase and the current solution we have is this:
let ref = FIRDatabase.database().reference().child("messages")
func retrieveMessageAttributes() {
ref.queryOrderedByKey().queryEqual(toValue: "uniqueFirebaseID").observe(.value, with: { (snapshot) in
print(snapshot)
for item in snapshot.children {
let data = (item as! FIRDataSnapshot).value! as! NSDictionary
print("*********************")
print((data["text"])!)
}
})
This prints all of the data for the specific set in the console, however it seems strange to have to retrieve data, convert it to a hash, and then read specific values.
So we were wondering if anyone else has an alternative method, or ORM, which is simpler and cleaner.

Resources