Firebase Firestore database data saving two time when pressing button - ios

Hello i'm doing to do app with firebase. But i try to save my task, task saving two times or more. So i'm see my task two times at collection view cell.Sometimes my data saving one time sometimes saving two times.
For example.
I don't know what's problem.
#objc func addTaskButtonClicked() {
if NewTaskViewController.textView.text == "" {
//alert
}else {
guard let currentUid = Auth.auth().currentUser?.uid else {return}
guard let text = NewTaskViewController.textView.text else {return}
let taskId = NSUUID().uuidString
let data = [
"text" : text,
"timestamp" : Timestamp(date: Date()),
"taskId" : taskId,
] as [String : Any]
Firestore.firestore().collection("tasks").document(currentUid).collection("ongoing_tasks").document(taskId).setData(data)
}
// NewTaskViewController.textView.text = ""
}
This is my saving function task at firebase. Why saving multiple times i have zero idea. Thanks for help.

Related

How to order firebase realtime data same as it is stored in firebase? [duplicate]

This question already has answers here:
Firebase getting data in order
(4 answers)
Closed 2 years ago.
I have tried everything but data is not coming in order , even snapshot is showing data in unordered form
I'm developing chat app. i want to maintain the order of the data so chat must show in order, but its not happening. whenever i send message it placed between the old messages not in the bottom
Here the latest message "what are you doing?"
here's the chat. look where is that latest message "what are you doing?"
my code for observing messages
public func getAllMessagesForConversation(with id: String, completion: #escaping (Result<[Message], Error>) -> Void) {
database.child("\(id)").queryOrdered(byChild: "date").observe(.value) { (snapshot) in
guard let value = snapshot.value as? [String:Dictionary<String, Any>] else{
completion(.failure(DatabaseError.failedToFetch))
return
}
var messages = [Message]()
for i in value {
guard let message = i.value["message"] as? String else {return}
let senderId = i.value["sender"] as? String
let recieverId = i.value["reciever"] as? String
let messageID = i.value["id"] as? String
var DATE = Date()
if let date = i.value["date"] as? String {
DATE = ChatViewController.dateFormatter.date(from: date)!
}
var kind: MessageKind?
kind = .text(message)
let finalKind = kind
let sender = Sender(photoURL: "",
senderId: senderId!,
displayName: Api.Params.inputUserName)
let abc = Message(sender: sender,
messageId: messageID ?? "",
sentDate: DATE,
kind: finalKind!)
messages.append(abc)
if messages.count == value.count {
messages = messages.sorted(by: { (lhs, rhs) -> Bool in
lhs.sentDate.toMillis() > rhs.sentDate.toMillis()
})
}
print(messages)
}
completion(.success(messages))
}
}
The problem is probably that you did not include key "date" to be indexed in firebase.
Go to firebase console database, go to Rules tab, enter and save:
"emantrasports":{
"$conversationID": {
".indexOn": ["date"]
}
}
There is a different approach to order your data on client side.
Firstly you need to create a model of your Message, that contains all necessary variables and also a date variable. And then order your array of message objects by your date variable.

Swift Firebase Save/Update multiple parents with the same child values

I have an array of strings which is the "uid's" of users. I am trying to append data/children to these multiple "uid's". Adding children or updating children to individual parents/users is easy and I understand how to do it. The problem is that this array can either contain 1 uid or 50 uid's. Is it possible for me to take these uid's and then update them with the same value? I am unsure what code to provide since I am just trying everything to attack this.
With the code below, this is me send a message to other users.
Array of uid strings
var data = [String]()
Sample code of me sending a message to 2 users, just wanted to provide something here to show I know how to update/save data
private func sendMessageWithProperties(_ properties: [String: Any]) {
let businessRef = Database.database().reference().child("Business Group Chats Messages").child((group?.uid)!).child((Auth.auth().currentUser?.uid)!)
let ref = Database.database().reference().child("Business Group Chats Messages").child((Auth.auth().currentUser?.uid)!).child((group?.businessName)!)
let businesChildRef = businessRef.childByAutoId()
let childRef = ref.childByAutoId()
let fromID = Auth.auth().currentUser!.uid
let timeStamp = Int(Date().timeIntervalSince1970)
var value:[String: Any] = ["fromId" : fromID, "timeStamp" : timeStamp, "name": self.loggedInUserData?["name"] as? String]
properties.forEach { (k,v) in
value[k] = v
}
childRef.updateChildValues(value) { (err, ref) in
if err != nil {
print(err!)
return
}
Database.database().reference().child("Business Group Chats").child((self.group?.uid)!).child((Auth.auth().currentUser?.uid)!).updateChildValues(["last message" : childRef.key!, "timestamp" : timeStamp, "businessName":(self.group?.businessName)!])
Database.database().reference().child("Business Group Chats").child((Auth.auth().currentUser?.uid)!).child((self.group?.uid)!).updateChildValues(["last message" : childRef.key!, "timestamp" : timeStamp])
self.inputContainerView.inputTextField.text = nil
}
}
Here is me taking that array of "uid's" and then pulling and printing that I can access each "uid" through a array of strings. Allowing me to access, now I can append data to each.
Database.database().reference().child("Businesses").observe(.value, with: { snapshot in
if snapshot.exists() {
self.businessUID = snapshot.value as? NSDictionary
if let dict = snapshot.value as? NSDictionary {
for item in dict {
let json = JSON(item.value)
let businessUid = json["uid"].stringValue
for uid in self.data {
if uid == businessUid {
//print(uid)
self.businessessuids = uid
print(self.businessessuids)
Database.database().reference().child("Businesses").child(self.businessessuids).observe(.value, with: { snapshot in
print(snapshot)
print("Trying to pull data from multiple strings right here this shoudld work")
})
print("printing the values to match with the string UID's")
}
}
}
}
} else {
print("does not exist")
}
})

delete table view cell from Firebase

I have a chat app. A user can choose another user in the app to chat with. Once a user sends a message to another user, their appended messages is displayed in a table view controller and when you click on the table view cell you are segued into a detailed controller.
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
let convoRef = FIRDatabase.database().reference().child("messages").child(convoId!)
let itemRef = rootRef.child("messages").child(convoId!).childByAutoId() // 1
let messageItem = [ // 2
"senderId": senderId!,
"ReceiverId": senderDisplayName!,
"text": text!,
"timestamp": NSDate().timeIntervalSince1970,
"convoId": convoId!
] as [String : Any]
itemRef.setValue(messageItem) // 3
JSQSystemSoundPlayer.jsq_playMessageSentSound() // 4
finishSendingMessage() // 5
isTyping = false
}
So only the two users chatting can view the messages. But I want one user to be able to delete the table view cell in their app (the messages) but when they do that it deletes in Firebase and that means it deletes in the other user's app too but I want it to only delete in the user that deleted the message. Here is how the messages are appended to be displayed in the tableview controller
func loadData()
{
FIRDatabase.database().reference().child("messages").observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for post in postsDictionary {
let messages = post.value as! [String: AnyObject]
for (id, value) in messages {
let info = value as! [String: AnyObject]
let convoId = info["convoId"]
let toId = info["ReceiverId"] as! String!
let fromId = info["senderId"] as! String!
if (toId == self.loggedInUserUid || fromId == self.loggedInUserUid) {
let ref = FIRDatabase.database().reference().child("messages").child(convoId as! String)
ref.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let message = Message(dictionary: dictionary)
if let receiver = message.convoId {
self.messagesDictionary[receiver] = message
self.messages = Array(self.messagesDictionary.values)
print(self.messages)
self.messages.sort(by: { (message1, message2) -> Bool in
return (message1.timestamp?.int32Value)! > (message2.timestamp?.int32Value)!
})
}
//this will crash because of background thread, so lets call this on dispatch_async main thread
DispatchQueue.main.async(execute: {
self.MessageTableView.reloadData()
})
}
}, withCancel: nil)}
}
}}})
}
If an update is made to a Firebase Realtime Database node, then any user reading on that node would see the same value ( if the security rules allow the user to read that node ). So, to answer your question - you can't really make user specific updates to a database path.
Having said that, in your case, you can do one of the following :-
You can delete what you're trying to delete on the client side, so that it won't be visible any longer. However, that won't delete the data in the database, and you would see it again the next time you download from the same path.
You can have separate conversation paths for both the users, and store the same messages in both these locations instead of a common one. In this method, you'd be duplicating data. But if you want to make user-specific changes, then this would more than do it for you.

Firebase - Atomic writes Across Multiple Locations not working

How can I make this function to update the realtime database in Firebase? I managed to make the function work but only to write new ID's for posts when updateChild is triggered. How can I make it to update the current posts by their post ID?
var pathToPictures = [Pictures]()
func updateAllImagesOfCurrentUserInDatabase() {
//refrences
let ref = FIRDatabase.database().reference()
let uid = FIRAuth.auth()?.currentUser?.uid
//match the user ID value stored into posts with current userID and get all the posts of the user
let update = ref.child("posts").queryOrdered(byChild: "userID").queryEqual(toValue: uid)
update.observe(FIRDataEventType.value, with: { (snapshot) in
// print(snapshot)
self.pathToPictures.removeAll()
if snapshot.key != nil {
let results = snapshot.value as! [String : AnyObject]
for (_, value) in results {
let pathToPostPicture = Pictures()
if let pathToImage = value["pathToImage"] as? String , let postID = value["postID"] as? String {
pathToPostPicture.postImageUrl = pathToImage
pathToPostPicture.postID = postID
self.pathToPictures.append(pathToPostPicture)
print("Image and POST ID: \(pathToPostPicture.postImageUrl!)")
print("Post ID is : \(postID)")
if FIRAuth.auth()?.currentUser?.uid == uid {
ref.child("Users_Details").child(uid!).child("profileImageUrl").observeSingleEvent(of: .value, with: { (userSnapshot) in
print(userSnapshot)
let userIMageUrl = userSnapshot.value as! String
pathToPostPicture.postImageUrl = userIMageUrl
self.pathToPictures.append(pathToPostPicture)
print("This is the image path:" + userIMageUrl + String(self.pathToPictures.count))
// Generate the path
let newPostRef = ref.child("posts").childByAutoId()
let newKey = newPostRef.key as String
print(newKey)
let updatedUserData = ["posts/\(postID)/pathToImage": pathToPostPicture.postImageUrl]
print("This is THE DATA:" , updatedUserData)
ref.updateChildValues(updatedUserData as Any as! [AnyHashable : Any])
})
}
print(self.pathToPictures.count)
}
}
} else {
print("snapshot is nill")
}
self.collectionView?.reloadData()
})
}
UPDATE: This is how my database looks like
This is my database:
"Users_Details" : {
"aR0nRArjWVOhHbBFB8yUfao64z62" : {
"profileImageUrl" : "url",
"userID" : "aR0nRArjWVOhHbBFB8yUfao64z62"
},
"oGxXznrS2DS4ic1ejcSfKB5UlIQ2" : {
"profileImageUrl" : "url",
"userID" : "oGxXznrS2DS4ic1ejcSfKB5UlIQ2"
}
},
"posts" : {
"-KlzNLcofTgqJgfTaGN9" : {
"fullName" : "full name",
"interval" : 1.496785712879506E9,
"normalDate" : "Tue, 06 Jun 2017 22:48",
"pathToImage" : "url",
"userID" : "oGxXznrS2DS4ic1ejcSfKB5UlIQ2"
},
"-KlzNXfvecIwBatXxmGW" : {
"fullName" : "full name",
"interval" : 1.496785761349721E9,
"normalDate" : "Tue, 06 Jun 2017 22:49",
"pathToImage" : "url",
"userID" : "oGxXznrS2DS4ic1ejcSfKB5UlIQ2"
},
Let me tell you what it does: It's looping through the posts and finds all the current user posts. Then it takes the path to image url of every post and assign it to an array. After that it's finding the current user path to image url and updates the whole array with that. Now, I don't know how to update the database with the new values. If anybody knows it will be much appreciate it!
I am looking to make Atomic Writes Across Multiple Locations. Can somebody show me how to fix my code to do that?
It will look something like this:
// ATOMIC UPDATE HERE - IF SOMEBODY CAN FIND THE RIGHT WAY OF DOING THAT
// Generate a new push ID for the new post
let newPostRef = ref.child(byAppendingPath: "posts").childByAutoId()
let newPostKey = newPostRef.key
// Create the data we want to update
let updatedUserData = ["posts/\(newPostKey)": ["pathToImage": self.pathToPictures]] as [String : Any]
// Do a deep-path update
ref.updateChildValues(updatedUserData)
It looks like you're going to be downloading a lot of repetitive data. While denormalization of your database is a good practice, I would argue that in this case, you're better off not including the same download URL in every post. It doesn't make a difference across a handful of posts, but if you have thousands of users and tens or hundreds of thousands of posts, that's a lot of extra data being downloaded. Instead, you could have a dictionary containing uids as keys and the profile imageUrl as the value. You could check the dictionary for the desired uid, and if it's not present, query the database for that user's User_Details, then add them to the dictionary. When you need to display the image, you'd get the url from this dictionary. Someone else may have a better suggestion for this, so I welcome other ideas.
If you'd prefer to keep the profile image in every post, then I recommend using Cloud Functions for Firebase. You can make a function that, when profileImageUrl is updated in the user's User_Details, then updates this entry in the other posts. Currently, Cloud Functions are only available in Node.js. If you don't know JS, please don't let that be a deterrent! It's definitely worth it to learn a bit a JS. And the samples show you about as much JS as you'll have to know to get started.
Check out these resources:
Getting Started with Cloud Functions for Firebase
GitHub samples
Cloud Functions for Firebase documentation
Writing a Database Trigger
Writing a Cloud Storage Trigger: Part 1
Writing a Cloud Storage Trigger: Part 2
After struggling with the logic for a few days I finally got it. If anyone will encounter something like this, this is the answer. In my case it's about updating all the posts of the current user when user changes his image (my posts use denormalisation so every path to image is different). It will loop through the posts, find the current user postsID matching his UserID, place them into an array, finds the currentUser picture and update that array with the path to image. Finally will update the Firebase Database!
var pathToPictures = [Pictures]()
func updateAllImagesOfCurrentUserInDatabase() {
//refrences
let ref = FIRDatabase.database().reference()
let uid = FIRAuth.auth()?.currentUser?.uid
//match the user ID value stored into posts with current userID and get all the posts of the user
let update = ref.child("posts").queryOrdered(byChild: "userID").queryEqual(toValue: uid)
update.observe(FIRDataEventType.value, with: { (snapshot) in
self.pathToPictures.removeAll()
if snapshot.value as? [String : AnyObject] != nil {
let results = snapshot.value as! [String : AnyObject]
for (_, value) in results {
let pathToPostPicture = Pictures()
if let pathToImage = value["pathToImage"] as? String , let postID = value["postID"] as? String {
pathToPostPicture.postImageUrl = pathToImage
pathToPostPicture.postID = postID
self.pathToPictures.append(pathToPostPicture)
print("Image and POST ID: \(pathToPostPicture.postImageUrl!)")
print("Post ID is : \(postID)")
if FIRAuth.auth()?.currentUser?.uid == uid {
ref.child("Users_Details").child(uid!).child("profileImageUrl").observeSingleEvent(of: .value, with: { (userSnapshot) in
print(userSnapshot)
let userIMageUrl = userSnapshot.value as! String
pathToPostPicture.postImageUrl = userIMageUrl
self.pathToPictures.append(pathToPostPicture)
print("This is the image path:" + userIMageUrl + String(self.pathToPictures.count))
// Update the Database
let postIDCount = pathToPostPicture.postID!
let updatedUserData = ["posts/\(postIDCount)/pathToImage": pathToPostPicture.postImageUrl!]
print("This is THE DATA:" , updatedUserData)
ref.updateChildValues(updatedUserData as Any as! [AnyHashable : Any])
})
}
print(self.pathToPictures.count)
}
}
} else {
print("snapshot is nill - add some data")
}
self.collectionView?.reloadData()
})
}

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