Swift Firebase database overwriting - ios

I am making a real-time messenger using Firebase. Currently, whenever I press a button I want a new message to be appended to the channel with the index of the message, but currently, whenever I press the button a new message is created that overwrites the old message. I know that setValue is usually the issue, but I really cannot tell what I'm doing wrong. What the database looks like before I add my new message. This is what it looks like after I add a new message here, and then the code I am using to add to the database.
#IBAction func sendMessageTapped(_ sender: Any) {
if messageTextField.text == "" {
print("blank")
return
} else {
// First we will update the amount of messages that the channel has.
ref.child("channels").child(channelName!).setValue(["numberOfMessages" : numberOfMessages+1 ])
numberOfMessages += 1
// after we have updated the amount of messages we will try to create a new message.
ref.child("channels").child(channelName!).child("messages").child(String(numberOfMessages)).child("message").child("content").setValue(messageTextField.text)
ref.child("channels").child(channelName!).child("messages").child(String(numberOfMessages)).child("message").child("name").setValue("Buddy")
}
}

ok, Firebase is not a traditional table based database, is a DOCUMENT based database. At the very top you have a thing called a "collection" which is just a list of "document" things. In your case, you'd have several collection things to serve as channels: "General", "TopicQ", "InterstingStuff" etc, and within them each message as a document. No need to have a document, to then list the messages within it.
Second, you don't need indexes as you're using them, make the message id an attribute of the message, because firebase support querying by field, and even then is questionable because if you make each message a document, they will have their own auto generated id's if you want.
Third, in your code you're rewriting the whole document each time, this is why you lose your previous messages, so if you keep it, you need to add a merge option:
// Update one field, creating the document if it does not exist.
db.collection("cities").document("BJ").setData([ "capital": true ], merge: true)

you probably want to do something like this. This is what I did for my app, hope this helps someone. This rootRef.childByAutoId() generates a new entry with unique id. You can use this as reference for your case.
let rootRef = Database.database().reference(withPath: "channels")
let childRef = rootRef.childByAutoId()
let values = ["Type": self.textField.text!, "message": self.textView.text!] as? [String : Any]
childRef.updateChildValues(values)

Related

Delete members of list when deleting list in Core Data / iOS / Swift 5

I have two entities: Item (which keeps track of lists) and Tasks (which are task items within lists). In one view of the app, there is a swipe to delete feature which removes the list. This works with the following code:
offsets.map { items[$0] }.forEach(viewContext.delete)
I would like to delete now obsolete tasks for the list being deleted. I first tried this code:
tasks.filter{$0.listID! == listsID}.forEach(viewContext.delete)
I was proud of myself. What an elegant solution. Swift hated it. I get an error that says
Reference to member 'listID' cannot be resolved without a contextual type
I Googled and SOd and got nowhere. I don't know what that error means and can't figure out how to fix it in XCode 12 / iOS 14. So then I came up with the following not so elegant code:
let listsID = offsets.map {items[$0].id!}
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = Tasks.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "listID == %#", listsID)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
deleteRequest.resultType = .resultTypeObjectIDs
do {
let result = try viewContext.execute(deleteRequest)
guard
let deleteResult = result as? NSBatchDeleteResult,
let ids = deleteResult.result as? [NSManagedObjectID]
else {return}
let changes = [NSDeletedObjectsKey: ids]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes,
into: [viewContext])
}
catch {
print(error as Any)
}
listsID captures the UUID of the list and stores it as a variable. However, you'll note that it is stored as an ARRAY (ugh). The fetchRequest.predicate code filters the tasks so that only those that have the attribute of 'listID' (which helps connect the task to the list it belongs to) matching the id of the list being deleted is pulled.
The code compiles (yay!). Then I get the following error when trying to delete a list:
Exception NSException * "Unexpected or improperly formatted UUID parameter with type Swift.__SwiftDeferredNSArray, expected NSUUID or well-formed NSString" 0x0000600003145b30
I'm sure there is a simple way to do this. I played with inverse relationships in Core Data but got nowhere. I didn't know how to tell it that 'id' (UUID) in 'Item' == 'listID' (UUID) in 'Tasks', so deleting the list didn't do anything to the tasks that belong to it.
I tried to create a ForEach loop, but ran into various errors that I couldn't resolve. I'd prefer to use the elegant code that I wrote at the top to make the deletion happen. Any ideas?
Thank you.
The credit for this goes to pbasdf for his super helpful responses. I used this link to learn about setting up relationships in Core Data: Seneca SDDS. I was missing one line in my persistence model:
newTask.list = newItem
How I solved this problem (in case it helps someone else) is that I created a one to many relationship for Item (the list of lists), and one to one relationship for Tasks (tasks are a subset of a list). Now, when I create a new task in the persistence script, I have a pointer back to the list it belongs to - hence the single line of code above.
The additional background that helped me understand Core Data relationships can be found in the link above. This snippet in particular was very helpful (in case the page goes 404):
Adding a new object, and setting the relationship
Assume that you have a reference to a Company object already; its variable name is c. How do you add a new Employee or Product? Create the new object and set its relationship. The relationship can be configured from either direction. In this section, we will configure it from the perspective of the just-added new object. For example:
// As noted above, assume that you have a reference
// "c" to an existing Company object...
// Create and configure a new employee
let peter = Employee(context: m.ds_context)
peter.name = "Peter McIntyre"
peter.age = 23
// etc.
// Now, set the relationship
peter.company = c
m.ds_save()
That’s it. If it seems too easy, well, it is easy.

Parse Platform on iOS: best way to replace changed local values with more-recently changed server values?

So imagine the following scenario, using the Parse platform on iOS:
I get a PFObject from the server, let's call it GlassChalice.
Someone else, let's say Bill Blofeld, changes GlassChalice from a different location.
Later, I make some changes to my local GlassChalice, but don't save them to the server.
Still later, I want to update GlassChalice, but I want to update it to the current server values, in other word Bill Blofeld's values. I do not want to replace the server values with my local values, and also do not want to reset my local values to the values GlassChalice was loaded with.
So if I use revert(), will I get what I want?
According to the Parse docs:
- revert Clears any changes to this object made since the last call to save and sets it back to the server state.
...but, as in my example, clearing "changes made since the last call to save" and setting it "back to the server state" aren't always the same thing.
So far this seems like the only way to guarantee the results I want, but it has one obvious problem:
public func updateObjectFromServer(_ objectToUpdate: PFObject, then doThis: (()->Void)? = nil) {
let query = PFObject.query()
query?.whereKey("objectId", equalTo: objectToUpdate.objectId!)
query?.getFirstObjectInBackground (block: {
(serverObject, error) in
if error.isNil() {
objectToUpdate["numberOfLimbs"] = serverObject?["numberOfLimbs"]
objectToUpdate["eyePlacement"] = serverObject?["eyePlacement"]
objectToUpdate["crossStitchingTalentRating"] = serverObject?["crossStitchingTalentRating"]
objectToUpdate["clamsEaten"] = serverObject?["clamsEaten"]
} else {
//handle error...
}
doThis?()
})
}
But the huge problem here is that I have to know all the key names, and type them in explicitly, for this to work.
Is there a better, more generic, way?

Problem with logic when it comes to storing dictionary values in firestore

I am trying to re-create the instagram follower and following functionality but there seems to be an error in logic. There is no error in code. What I am doing right now when the "follow" button is pressed, is to create a collection called "user-following", add the "current UID" as "document ID" and then store the following target users data as [targetuid: 1]. At the same time, I create a collection called "user-followers", add the "Target UID" as "document ID" and then store the followers data as [currentuid:1]. The issue here is that it works when it comes to following just one user. When I try to follow another, the existing following user data gets overridden with the new user whom I just followed instead of appending the data.
Example: Assume currentUser is A, user1 = B, user2 = C
When I follow user1 and user2 my database in firestore should be reflected as:
user-following -> A -> [B:1,C:1]
user-followers -> B -> [A:1]
user-followers -> C -> [A:1]
The above translates to collection -> documentID -> dictionaryvalues
The problem is that I do not get the above result. Instead when I follow user2, the value of user1 gets overridden. So there is only one dictionary value stored.
I know that the problem lies somewhere in the way I am trying to create a field under the document ID. I know I can create a dictionary and append values but I think that's a lot of code for an otherwise simple solution.
func follow(){
guard let currentUid = Auth.auth().currentUser?.uid else
{return}
guard let uid = uid else {return}
self.isFollowed = true
// Add followed user to current user-following structure
Firestore.firestore().collection("user-
following").document(currentUid).setData([uid:1])
// Add current user to followed user-follower structure
Firestore.firestore().collection("user-
followers").document(uid).setData([currentUid:1])
}
Never mind. Realised that I had to use merge.
Firestore.firestore().collection("user-
following").document(currentUid).setData([uid:1], merge: true)

How do I set the name a child in Firebase to user input from a text field?

Here's how I want my data tree to look in Firebase:
users:
--Travis:
------travisData:
--Stephanie:
------stephanieData:
My problem is that I can't seem to name a child using user input from a textfield.
Here's the code I have so far:
ref = Database.database().reference()
let username = String(emailTextField.text!)
//ref.child("users").setValue(["username": username])
ref.child("users").setValue(["\(username)": calendar])
The commented out line creates a child named "username" with the textfield.text as its content. But how can I create a child whose name is the textfield.text, with other data as its content? I've tried multiple combinations of using .child and .setValue, but everything keeps throwing me an error when I try to use the username variable instead of a plain string.
Here's the error I get:
Your problem is that Firebase doesn't accept the "." symbol in their user names. You would get the same problem even if you typed a string of letters with a "." such as "adfojnajsdnfjs.fjknasd" instead of "username".
I think you're looking for:
ref.child("users").child(username).setValue(calendar)
Although that depends on the value of calendar.
At the very least this should work:
ref.child("users").child(username).setValue(true)
And give you the following JSON:
"users": {
"Travis": true
}
To write a more complex structure, you can for example do:
ref.child("users").child(username).child("name").setValue(username)
Which results in:
"users": {
"Travis": {
name: "Travis"
}
}
As your require, I think this code will help you
ref.child("users").child("\(inputUsername)").updateChildValues(["travidDataKey": userData])
And
result image
users:
Travid2:
travidDataKey:"Travid Data"

databaseRef Firebase withoud childByAutoID()

Guten Tag,
i'm uploading some text to firebase through my iOS Application, but i can't find a code, where i can give the App User not a random ID, but maybe the email for the id to upload or Name. I hope you understand what i mean.
Current Code:
let logininit : [String : AnyObject] = ["text4" : text4!, "text5" : text5!, "text6" : text6!]
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Logins").childByAutoId().setValue(logininit)
Now i want to remove this childByAutoID, and set it to, how i said, email or name of the user.
Firebase Database
EDIT
Resolved this Problem: databaseRef.child("Logins").child(The Email That You Want Here).setValue(logininit)
I think I understood what you meant, but I don't see reasons for you to do that. Usually when you have a new entry on some entity, you usually gives it a unique ID (UUID). So, I think if you use child(The Email That You Want Here), should work as you want:
databaseRef.child("Logins").child(The Email That You Want Here).setValue(logininit)

Resources