Firebase chat issue - two messages at the same time - ios

I'm new here and I just can't figure how to solve this:
func sendMessage(msgText : String) {
let defaults = NSUserDefaults.standardUserDefaults()
let myRootRef = Firebase(url:"https://boiling-heat-3478.firebaseio.com/")
self.messageCount = String(Int(self.messageCount)!+1)
let messagesRef = myRootRef.childByAppendingPath("messages")
let messageIDRef = messagesRef.childByAppendingPath(self.messageCount)
let messageInfo = ["Text": self.messageText.text!, "SenderID": defaults.objectForKey("userID") as! String]
messageIDRef.updateChildValues(messageInfo)
}
On view did load:
let myRootRef = Firebase(url:"https://boiling-heat-3478.firebaseio.com/")
let messagesRef = myRootRef.childByAppendingPath("messages")
let ref1 = Firebase(url: String(messagesRef))
// Attach a closure to read the data at our posts reference
ref1.queryOrderedByKey().queryStartingAtValue(self.messageCount).observeEventType(.ChildAdded, withBlock: { snapshot in
dispatch_async(dispatch_get_main_queue()){
let path = String(snapshot.ref)
let parts = path.characters.split("/")
if(Int(self.messageCount) < Int(String(parts.last!))){ self.messageCount = String(Int(self.messageCount)!+1) }
let messageText = snapshot.value.objectForKey("Text") as! String
let senderNameAge = snapshot.value.objectForKey("SenderID") as! String
let msg = Message(nameAge: senderNameAge, image: nil, text: messageText)
self.messages.append(msg)
self.messageTableView.reloadData()
}
}, withCancelBlock: { error in
print(error.description)
})
Well, this is working fine. I can chat with 1 person and see replies in real-time, but when I try to send like "aaaa" from person 1 to 2 and "bbbb" from person 2 to 1: message "aaaa" shows only on person 1 screen and "bbbb" on person 2.
Also, I can see on firebase that one of those messages replaced the other on the same destination! How can I avoid or solve this? Thank you

You're creating a problem by trying to use a count to index your messages.
The Firebase documentation explicitly warns against using arrays, since (as you're finding out) it is difficult to keep the message count in sync between multiple clients.
Instead of keeping the count synchronized between clients, you can simply call childByAutoId on a Firebase reference to generate a new child ID that is guaranteed to be unique and always incrementing.
let messagesRef = myRootRef.childByAppendingPath("messages")
let messageIDRef = messagesRef.childByAutoId()
let messageInfo = ["Text": self.messageText.text!, "SenderID": defaults.objectForKey("userID") as! String]
messageIDRef.updateChildValues(messageInfo)
See this section of the documentation for more information and examples.
Also see this example of a chat app written in Swift, that you can use for inspiration.

Related

Fetch Firebase inside another fetch

So I am creating a forum inside my app and when fetching the topics on the database I also need to fetch some user info like picture and username of the original poster to put it on the table view cells.
To keep the recommendation of keeping the Firebase database flat I have the messages on a separate ref searchable by key and NOT as a child of topics.
I can't keep something like opImage and opUsername as childs of topics because if a user changes its username or profile image I would need to change it on every topic he ever participated as well.
What is the best way to handle this?
The method to fetch from Firebase would look something like this. The problem with that implementation is that the Firebase calls are asynchronous and there would be no guarantee that the image would be attached to the correct topic.
DataService.ds.Topics_Base.child(cat.key!).observeSingleEvent(of:.value, with: { (snapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {//snapshots = all topics
for top in snapshots{
if let dict = top.value as? Dictionary<String,AnyObject>{
guard let title = dict["title"] as? String,let text = dict["text"] as? String,let time = dict["time"] as? String,let username = dict["username"] as? String,let timeSimple = dict["time-simple"] as? String,let lastPost = dict["last-post"] as? String,let open = dict["open"] as? Bool else{
continue
}
let newTopic = Topic(subject: title, text: text, time: time, topicKey: top.key, username: username)
self.allTopics.append(newTopic)
print(self.allTopics.count)
if let email = dict["email"] as? String{
//FETCH USER INFO FROM EMAIL FETCHED
let validEmail = HelperMethods.removeSpecialCharactersFromEmail(email: email)
DataService.ds.Users_Base.child(validEmail).observeSingleEvent(of: .value, with: {(snapshot) in
if let userDict = snapshot.value as? Dictionary<String,AnyObject>{
if let imgUrl = userDict["profile_image_url"] as? String{
self.allTopics[currentIndex].opImageUrl = imgUrl
}
self.allTopics.append(newTopic)
if (totalTopics == snapshots.count){
DispatchQueue.main.async {
self.allTopics.sort { $0.lastPostTime! > $1.lastPostTime!}
self.tableView.reloadData()
self.refreshControl.endRefreshing()
}
}
}
})
}
} }
}
}
)
}
Thanks in advance.
I ended up deciding to fetch the user info AFTER fetching all topics and then looping through the topics array and attaching user info for each topic.
EXAMPLE:
1 - fetch all topics and create array
2 - for each topic in array{
fetch user on Firebase using a property like topic.email
set something like:
topic.imageUrl = user.imageUrl
topic.username = user.username
}
reload table view

Can't populate an array with data from Firebase database

Below I try to make an array ChatListings, but it doesn't work.
let chatRef = FIRDatabase.database().reference().child("chatListings")
override func viewDidLoad() {
super.viewDidLoad()
let firstQuery = chatRef.queryOrdered(byChild: "userID").queryEqual(toValue: userID)
firstQuery.observe(FIRDataEventType.value, with: { snapshot in
for child in snapshot.children {
print("child is \(child)")
if let dict = snapshot.value as? Dictionary<String, AnyObject>{
print("dict is \(dict)")
let roomKey = dict["chatRoomKey"] as! String
let oUID = dict["otherUserID"] as! String
let oUserName = dict["otherUserName"] as! String
let oProfilePic = dict["otherUserProfilePic"] as! String
let userIDTemp = dict["userID"] as! String
chatListing = ChatListing(chatRoomKey: roomKey, UID: userIDTemp, name: oUserName, otherUserID: oUID, otherUserProfilePicURL: oProfilePic)
chatListings.append(chatListing)
}
}
print("chatListings = \(chatListings)")
})
}
This crashes saying that the compiler unexpectedly found nil while unwrapping an Optional value. I don't know why it won't work. I've tried every which way I can find to extract the data that the compiler reads moments before crashing or failing to fill an array of my 'chatlisting' objects.
Here's an example of the data that the compiler reads but cannot extract with maybe 4 different coding attempts:
"-KjdSF97Q2z3afXzkwQ9": {
chatRoomKey = "-KjdSF97Q2z3afXzkwQ9";
messages = {
"-KjdSOVTsg8jEy6SeEA2" = {
MediaType = PHOTO;
fileUrl = "https://firebasestorage.googleapis.com/v0/b/preollify.appspot.com/o/mget8KN2nHe4sOhbnWTixYvCOrr2%2F515963239.371526?alt=media&token=6cb12ec1-5bdb-43a1-ab49-90c90570b341";
senderId = mget8KN2nHe4sOhbnWTixYvCOrr2;
senderName = Michael;
};
"-KjdSPxpNT0pkQ1y5-_1" = {
MediaType = VIDEO;
fileUrl = "https://firebasestorage.googleapis.com/v0/b/preollify.appspot.com/o/mget8KN2nHe4sOhbnWTixYvCOrr2%2F515963229.282051?alt=media&token=04671c8e-d7f1-49f2-81d0-09836c034ae2";
senderId = mget8KN2nHe4sOhbnWTixYvCOrr2;
senderName = Michael;
};
"-KjdVaVTfbaC-3S-91-A" = {
MediaType = TEXT;
senderId = mget8KN2nHe4sOhbnWTixYvCOrr2;
senderName = Michael;
text = The;
};
};
otherUserID = aRandomUser3611;
otherUserName = Michael;
otherUserProfilePic = "https://firebasestorage.googleapis.com/v0/b/preollify.appspot.com/o/ProfilePictures%2Fmget8KN2nHe4sOhbnWTixYvCOrr2%2FmediumProfilePicture.jpg?alt=media&token=d88afa5d-0db7-4ce2-95c9-3038ff592e9f";
userID = mget8KN2nHe4sOhbnWTixYvCOrr2;
I'm trying to extract all the data but the messages part, which I plan on doing later in the app.
This data (excluding the "messages" part) gets written in the chatViewController's viewDidLoad like this:
let preMessageRef = chatRef.childByAutoId()
chatListingID = preMessageRef.key
let initialChatRoomData = ["chatRoomKey": chatListingID, "otherUserID": otherUID, "otherUserName": otherUserName, "otherUserProfilePic": otherUserProfilePicURLString, "userID": userID]
preMessageRef.setValue(initialChatRoomData)
Retrieving data from Firebase Database has been completely hit or miss for me, with copying the successful attempts of extracting data rarely working twice. Their documentation is minimal to the point of leaving out way too much as it provides little help for how to extract data in real world contexts. Why do people like Firebase? It has been a very frustrating experience working with Firebase and I definitely regret it. But it's probably too late to turn back and go with something better, i.e. a platform that provides clear instruction for how to get it to work.
I think you just have a silly typo. Try this:
let childData = child as! FIRDataSnapshot
print("child key: \(childData.key)")
if let dict = childData.value as? Dictionary<String, AnyObject> {
...
}
that is, use child instead of snapshot.
Update. Turns out using NSDictionary, rather than Dictionary, fixed the dict constant crashes. But, besides compiler bugs, still not clear why...

Firebase Swift syntax

Can anyone help me convert this from JavaScript to Swift 3 syntax?
I am trying to get all of the clients a specific user has.
I have clients saved in their own node, then I have a list of clientIDs in each of the users.
I believe this code will work, as a similar situation was described in the guide it comes from, data organized in the same way, but it is not swift syntax, particularly .on and .once.
var usersREF =
new Firebase("https://awesome.firebaseio-demo.com/users");
var clientsREF =
new Firebase("https://awesome.firebaseio-demo.com/clients");
var currentUsersClients = userREF.child("userID").child("comments");
currentUsersClients.on("child_added", function(snap) {
clientsREF.child(snap.key()).once("value", function() {
// Render the clients on the link page.
));
});
Here is the data Structure on Firebase:
I suppose other ways to do it might be to grab the current Users clientIDs, then do a call for all clients. Filter them via the clientIDs.
Or
Grab the current users clientIDs, then loop through them, making specific calls to each individual client using the ID.
I just don't know if it is bad practice to make multiple calls like that to firebase within a for loop. Or even worse, to pull down much much more data than is neccesary then filter it.
Thanks for any help!
This will get the clients for uid_0, based on your structure
let userRef = ref.child("users/uid_0")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let userDict = snapshot.value as! [String:AnyObject]
let clientsDict = userDict["clients"] as! [String:AnyObject]
for client in clientsDict {
let clientId = client.key
print(clientId)
}
})
Use this sample code to get your desired result
func getUsers(forUserID userID: String, completion: #escaping (User) -> Swift.Void, failure: #escaping () -> ()) {
if Auth.auth().currentUser != nil {
Database.database().reference().child("users").child(userID).observe(.childAdded, with: { (snapshot) in
if snapshot.exists() {
let receivedMessage = snapshot.value as! [String: Any]
let name = receivedMessage["name"] as? String ?? ""
let id = receivedMessage["id"] as? Double ?? 0.0
let profileurl = receivedMessage["url"] as? String ?? ""
completion(User(name: name, id: id, url: url))
} else {
failure()
}
})
} else {
failure()
}
}

Unable to get keys from firebase database

I have been pondering for the longest time in my student programmer life. I would like to know
I added the keys using autoChildId.
How to get keys from firebase database swift 2? I know how to get from Android using .getKeys()
My best friend, Google, taught me to use allKeys. However, my friendship is on the verge of in despair right now as I received the following msg that our relationship with .allKeys will always fail ( see image below). Haish...
I need this in order to show the data from Firebase Database into my tableview cos I believe this is the issue to a empty table just like how my heart is for my project. No heart.
Here is how my firebase database looks like:
Here is my code:
func findPlaceToEat(){
print("inside findPlaceToEat()")
print("Plan price level")
print(planPriceLevel)
print("End of price level")
ref = FIRDatabase.database().reference()
ref.child("places_detail").child("price_level").child(planPriceLevel).observeEventType(.ChildAdded, withBlock:{
(snapshot) in
if let dictionary = snapshot.value?.allKeys! as? [String: AnyObject]{
let PlaceObj = placeObj(place_name: dictionary["place_name"] as! String, place_type: dictionary["place_type"] as! String, price_range: dictionary["price_range"] as! String, vegan_type:dictionary["vegan_type"] as! String , website: dictionary["website"] as! String)
print("Whatever")
print(PlaceObj);
//self.tableView.reloadData()
}
}, withCancelBlock: nil)
}
to get key from snapshot
snapshot.key
I got a workaround for my project, everyone please pray that my lecturer don't see this. :
What I did was inside the save button I retrieve the value from database and then save it back into Firebase Database.
ref = FIRDatabase.database().reference()
ref.child("hello").child("google! I need a part time job").child(planPriceLevel).observeEventType(FIRDataEventType.ChildAdded, withBlock:{
(snapshot: FIRDataSnapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let getPlaceObj = placeObj()
getPlaceObj.setValuesForKeysWithDictionary(dictionary)
self.PlaceObj.append(getPlaceObj)
print("Name " ,getPlaceObj.place_name)
}
let place_name = snapshot.value?.objectForKey("place_name") as! String
let place_type = snapshot.value?.objectForKey("place_type") as! String
let price_range = snapshot.value?.objectForKey("price_range") as! String
let vegan_type = snapshot.value?.objectForKey("vegan_type") as! String
let website = snapshot.value?.objectForKey("website") as! String
print(place_name, place_type, price_range, vegan_type, website)
let savePlan : [String: AnyObject] = ["place_name":place_name, "place_type":place_type, "price_range":price_range, "vegan_type":vegan_type, "website":website]
self.ref.child("can you place hire me as your intern? I am from Singapore!!!").child(self.user!.uid).childByAutoId().setValue(savePlan)
}, withCancelBlock: nil)
You need to define query orderbykey like bellow:
this.afd.list('/yourItems/', {query:{orderByKey :true}}).subscribe((elements) => {
elements.map(element=>{
console.log(element.$key);
})
});

Firebase - iOS Swift: load table view cell with data retrieved from two separate child nodes

I'm building an app using Firebase that displays a list of Questions in a table view. Each table view cell holds the text of the question, the name of the user that posted it, and the profile photo of the user that posted it.
Here is how I've structured my JSON tree:
"questions"
"firebase_autoID_01"
"name": "Jim"
"text": "Why is the earth round?"
"userID": "123456"
"userInfo"
"userID": "123456"
"photoURL": "gs://default_photo"
I'm trying to retrieve the name and text for each question and the photoURL of the user with the corresponding userID to then place those 3 elements in each table view cell. So I'm trying to retrieve data from the questions child node AND the separate userInfo child node to put that data into the same table view cell. Here is the code I've been trying to do that with:
When a user first gets created, I set their photoURL to a default image that I manually uploaded to Firebase Storage:
...
let placeholderPhotoRef = storageRef.child("Profile_avatar_placeholder_large.png")
let placeholderPhotoRefString = "gs://babble-8b668.appspot.com/" + placeholderPhotoRef.fullPath
//let placeholderPhotoRefURL = NSURL(string: placeholderPhotoRefString)
let data = [Constants.UserInfoFields.photoUrl: placeholderPhotoRefString]
self.createUserInfo(data)
}
func createUserInfo(data: [String: String]) {
configureDatabase()
let userInfoData = data
if let currentUserUID = FIRAuth.auth()?.currentUser?.uid{
self.ref.child("userInfo").child(currentUserUID).setValue(userInfoData)
}
}
Issues start occurring when I try to retrieve data from both the questions and userInfo:
var photoUrlArray = [String]()
func configureDatabase() {
ref = FIRDatabase.database().reference()
_refHandle = self.ref.child("questions").observeEventType(.ChildAdded, withBlock: {(snapshot) -> Void in
self.questionsArray.append(snapshot)
//unpack the userID from the "questions" child node to indicate which user to get the photoURL from in the "userInfo" child node
if let uid = snapshot.value?[Constants.QuestionFields.userUID] as? String {
self._photoURLrefHandle = self.ref.child("userInfo").child(uid).observeEventType(.ChildAdded, withBlock: {(snapshot) -> Void in
if let photoURL = snapshot.value as? String {
self.photoUrlArray.append(photoURL)
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: self.questionsArray.count-1, inSection: 0)], withRowAnimation: .Automatic)
}
})
}
})
}
I get the following error in the Xcode console:
"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 (2) 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).'"
What I'm doing here is creating a firebase handle to observe/retrieve the users photoURL from the userInfo child, and that handle is created inside the closure of the handle that observes/retrieves the data from the questions child.
Questions: If this is not the right way to retrieve the data I want, how should I approach this? And how should I structure my JSON data if it isn't currently structure the right way?
Here is how I'm unpacking the name and text from the questions child node in the table view and it's working fine:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell! = self.tableView.dequeueReusableCellWithIdentifier("tableViewCell", forIndexPath: indexPath)
//unpack question from database
let questionSnapshot: FIRDataSnapshot! = self.questionsArray[indexPath.row]
var question = questionSnapshot.value as! Dictionary<String, String>
let name = question[Constants.QuestionFields.name] as String!
let text = question[Constants.QuestionFields.text] as String!
cell!.textLabel?.text = name + ": " + text
return cell!
}
Question: should I unpack the photoURL here or in the previous configureDatabase() function?
Since you asked for suggestion's on how to store your database structure in JSON. :-
Suggested JSON Structure : -
{"Users" : {
"userID_1" : {
userName : "Jim",
userImageURL : "gs://default_photo",
questionsAsked : {
"questionID_1" : "QUESTION TEXT",
"questionID_2" : "QUESTION TEXT"
}
}
"userID_2" : {
userName : "Moriarty",
userImageURL : "gs://default_photo",
questionsAsked : {
"questionID_1" : "QUESTION TEXT",
"questionID_2" : "QUESTION TEXT"
}
}
}
}
Since you are aiming to display the image and name of the user who posted the question with the question itself , i think it would be best if you created the child of questionAsked in the usersId itself that way you can keep track of the no and what questions were asked by that particular user.
Saving the user's profile data would go like this : -
FIRDatabase.database().reference().child("Users").child(FIRAuth.auth()!.currentUser()!.uid).setValue([
userName : "Jim",
userImageUrl : //URL that you want to save
])
//Instead of FIRAuth.auth()!.currentUser()!.uid you can use childByAutoId too but i wont recommend that.
When user makes a post call a function with this code : -
let rootRef = FIRDatabase.database().reference().child("Users").child(FIRAuth.auth()!.currentUser()!.uid).child("questionsAsked")
rootRef.observeEventType(.Value, withBlock: {(snap) in
if snap.exists(){
if let questionsDictionary = snap.value as? NSMutableDictionary{
questionsDictionary.setObject("Why is the earth round?", forKey: //your questionID)
rootRef.setValue(questionsDictionary)
}
}else{
rootRef.setValue([
your_questionID : "Why is the earth round?"
])
}
})
How to retrieve a question :-
let parentRef = FIRDatabase.database().reference().child("Users")
parentRef.observeEventType(.Value, withBlock: {(userSnap) in
if userSnap.exists(){
for eachUser in userSnap.value as! [String:AnyObject]{
let userId = eachUser.key as! String
parentRef.child(userId).observeEventType(.Value, withBlock: {(userSnap) in
if let userInfo = userSnap.value as? [String:AnyObject]{
let userNameRecieved = userInfo["userName"] as! String
let userImageUrlRecieved = userInfo["userImageURL"] as! String
parentRef.child(userId).child("questionsAsked").observeEventType(.Value, withBlock: {(usersQuestion) in
if userQuestion.exists(){
for eachQ in userQuestion.value as! [String:AnyObject]{
let question = eachQ.value as! String
//Create a function named - "initialiseCell" , that will initialise the datasource of the tableView with username, photoURL and question as its : String parameters
initialiseCell(question,userNameRecieved,userImageUrlRecieved)
}
}
})
}
})
}
}
})
As for yourQuestion_ID : -
let yourQuestionID = "\(Int(NSDate.timeIntervalSinceReferenceDate() * 1000))
This will produce an unique yourQuestionID every next second or maybe millisecond
This retrieve function in this stage, calls for every question in your entire apps database but you can filter it by modifying the code further...
This is how I ended up structuring my Firebase JSON tree:
"users":
"userID4321":
"displayName": "bob"
"photoURL": "gs://default_photo"
"questions":
"questionID5678":
"text": "How are you?"
"userID": "userID4321"
I then implemented the following code in my configureDatabase method:
func configureDatabase() {
ref = FIRDatabase.database().reference()
_refHandle = self.ref.child("questions").observeEventType(.ChildAdded, withBlock: {[weak self] (questionSnapshot) in
let questionID = questionSnapshot.key
var question = questionSnapshot.value as! [String: AnyObject]
question[Constants.QuestionFields.questionID] = questionID
let userID = question[Constants.QuestionFields.userID] as! String
let usersRef = self?.ref.child("users")
usersRef?.child(userID).observeEventType(.Value, withBlock: { (userSnapshot) in
var user = userSnapshot.value as! [String: AnyObject]
let photoURL = user[Constants.UserFields.photoUrl] as! String
let displayName = user[Constants.UserFields.displayName] as! String
question[Constants.QuestionFields.photoUrl] = photoURL
question[Constants.QuestionFields.displayName] = displayName
...
The observeEventType handle on the questions child gives me a snapshot of each question object every time a new child gets added or a new question gets created.
I then create a local dictionary from that question snapshot that was retrieved from the Firebase server.
With the userID from that question, I can then identify the corresponding user with another observeEventType method and retrieve that user's photoURL and append it to the question snapshot.
Once all the data I need is in the local question dictionary, I append it to my questionsArray which gets sent to the table view to display each question with the appropriate data.
Let me know if anyone who reads this would have done it differently!

Resources