iOS Firebase Cloud Firestore batch() .addDocument() - ios

Looking over the the firestore documentation on Batched write
https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes
But, it doesn't say anything about adding a document to a collection.
Anyone know how to do this?

The setData method (class reference documentation) creates a document if not already existing, or (by default) overwrites the data on that reference path if it already exists.
// Create or overwrite data for document with ID "NYC"
let nycRef = db.collection("cities").document("NYC")
batch.setData([:], forDocument: nycRef)
// Create a document with a random unique ID
let docRef = db.collection("places").document()
batch.setData([:], forDocument: docRef)

Related

How to structure data in Firestore using swift [duplicate]

The documentation does not have any examples on how to add a subcollection to a document. I know how to add document to a collection and how to add data to a document, but how do I add a collection (subcollection) to a document?
Shouldn't there be some method like this:
dbRef.document("example").addCollection("subCollection")
Edit 13 Jan 2021:
According to the updated documentation regarding array membership, now it is possible to filter data based on array values using whereArrayContains() method. A simple example would be:
CollectionReference citiesRef = db.collection("cities");
citiesRef.whereArrayContains("regions", "west_coast");
This query returns every city document where the regions field is an array that contains west_coast. If the array has multiple instances of the value you query on, the document is included in the results only once.
Assuming we have a chat application that has a database structure that looks similar to this:
To write a subCollection in a document, please use the following code:
DocumentReference messageRef = db
.collection("rooms").document("roomA")
.collection("messages").document("message1");
Creating a messages collection and calling addDocument() 1000 times will be expensive for sure, but this is how Firestore works. You can switch to Firebase Realtime Database if you want where the number of writes doesn't matter. But regarding Supported Data Types in Firestore, in fact, you can use an array because it is supported. In Firebase Realtime database you could also use an array, but this is an anti-pattern. One of the many reasons Firebase recommends against using arrays is that it makes the security rules impossible to write.
Cloud Firestore can store arrays, but it does not support querying array members or updating single array elements. However, you can still model this kind of data by leveraging the other capabilities of the Cloud Firestore. Here is the documentation where it is very well explained.
You also cannot create a subcollection with 1000 messages, add all of them to the database, and expect it to be considered a single record. It will be considered one write operation for every message, in total 1000 operations. The picture above does not show how to retrieve data, it shows a database structure in which you have something like this:
collection -> document -> subCollection -> document
Here's a variation where the subcollection is storing ID values at the collection level, rather than within a document where the subcollection is a field there with additional data.
This is useful for connecting a 1-to-Many ID mapping w/out having to drill through an additional document:
function fireAddStudentToClassroom(studentUserId, classroomId) {
var db = firebase.firestore();
var studentsClassroomRef =
db.collection('student_class').doc(classroomId)
.collection('students');
studentsClassroomRef
.doc(studentUserId)
.set({})
.then(function () {
console.log('Document Added ');
})
.catch(function (error) {
console.error('Error adding document: ', error);
});
}
Thanks to #Alex's answer
This answer a bit off from the original question here, where it explicitly asks for adding a collection to a document. However, after searching for a solution for this scenario and not finding any mention in docs or on SO, this post seems like a reasonable place to share the findings
Here's my code:
firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).set(wordData)
.then(function() {
console.log("Collection added to Firestore!");
var promises = [];
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('AudioSources').doc($scope.accentDialect).set(accentDialectObject));
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('FunFacts').doc($scope.longLanguage).set(funFactObject));
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('Translations').doc($scope.translationLongLanguage).set(translationObject));
Promise.all(promises).then(function() {
console.log("All subcollections were added!");
})
.catch(function(error){
console.log("Error adding subcollections to Firestore: " + error);
});
})
.catch(function(error){
console.log("Error adding document to Firestore: " + error);
});
This makes a collection EnglishWords, which has a document of. The document of has three subcollections: AudioSources (recordings of the word in American and British accents), FunFacts, and Translations. The subcollection Translations has one document: Spanish. The Spanish document has three key-value pairs, telling you that 'de' is the Spanish translation of 'of'.
The first line of the code creates the collection EnglishWords. We wait for the promise to resolve with .then, and then we create the three subcollections. Promise.all tells us when all three subcollections are set.
IMHO, I use arrays in Firestore when the entire array is uploaded and downloaded together, i.e., I don't need to access individual elements. For example, an array of the letters of the word 'of' would be ['o', 'f']. The user can ask, "How do I spell 'of'?" The user isn't going to ask, "What's the second letter in 'of'?"
I use collections when I need to access individual elements, a.k.a. documents. With the older Firebase Realtime Database, I had to download arrays and then iterate through the arrays with forEach to get the element I wanted. This was a lot of code, and with a deep data structure and/or large arrays I was downloading tons of data that I didn't need, and slowing my app running forEach loops on large arrays. Firestore puts the iterators in the database, on their end, so that I can request a single element and it sends me just that element, saving me bandwidth and making my app run faster. This might not matter for a web app, if your computer has a broadband connection, but for mobile apps with poor data connections and slow devices this is important.
Here are two pictures of my Firestore:
From the docs:
You do not need to "create" or "delete" collections. After you create the first document in a collection, the collection exists. If you delete all of the documents in a collection, it no longer exists.
Here i faced the same issue and solve with the answere of #Thomas David Kehoe
db.collection("First collection Name").doc("Id of the document").collection("Nested collection Name").add({
//your data
}).then((data) => {
console.log(data.id);
console.log("Document has added")
}).catch((err) => {
console.log(err)
})
too late for an answer but here is what worked for me,
mFirebaseDatabaseReference?.collection("conversations")?.add(Conversation("User1"))
?.addOnSuccessListener { documentReference ->
Log.d(TAG, "DocumentSnapshot written with ID: " + documentReference.id)
mFirebaseDatabaseReference?.collection("conversations")?.document(documentReference.id)?.collection("messages")?.add(Message(edtMessage?.text.toString()))
}?.addOnFailureListener { e ->
Log.w(TAG, "Error adding document", e)
}
add success listener for adding document and use firebase generated ID for a path.
Use this ID for the complete path for a new collection you want to add.
I.E. - dbReference.collection('yourCollectionName').document(firebaseGeneratedID).collection('yourCollectionName').add(yourDocumentPOJO/Object)
Okay so I recently faced a similar problem given the recent update in the firebase/firestore documentation.
And here is a solution that worked for me
const sendMessage = async () => {
await setDoc(doc(db, COLLECTION_NAME, projectId, SUB_COLLECTION_NAME, nanoid()), {
text:'this is a sample text',
createdAt: serverTimestamp(),
name: currentUser?.firstName + ' ' + currentUser?.lastName,
photoUrl: currentUser?.photoUrl,
userId: currentUser?.id,
});
}
You can find a similar example in the docs
https://firebase.google.com/docs/firestore/data-model#web-version-9_3
chat room
If you wish to listen for live update you can use a similar method as follows
const messagesRef = collection(db, COLLECTION_NAME, projectId, SUB_COLLECTION_NAME)
const liveUpdate = async () => {
const queryObj = query(messagesRef, orderBy("createdAt"), limit(25));
onSnapshot(queryObj, (querySnapshot) => {
const msgArr: any = [];
querySnapshot.forEach((doc) => {
msgArr.push({ id: doc.id, ...doc.data() })
});
console.log(msgArr);
});
}
There is no separate method to add sub-collection into the document.
You can just call the collection method itself.
If the collection exists it will reference that otherwise create a new one.
dbRef.document("example").collection("subCollection")

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)

When posting to parse server does the newly created object Id get returned

I am using parse server as the back end for an iOS app. A button tap "creates a post" that can have as many images uploaded with the post as the user queues up.
The issue is that I don't know the number of images that the user will upload so I would need to have 2 classes in the parse database: "Posts" for the post information, and "Images" for each image uploaded. How do I get the autogenerated id for the post so that I can associate the images to the post?
Is there a way in parse to return the new id that was generated after a successful insert? If not, is the only solution to insert and then run an immediate select for most recent post by that user? If the app is on more than one device that could lead to errors so I thought there may be a better approach.
I believe when a PFObject is saved and the success block is executed, you should be able to see the snapshot of the objectId there. As an example
//This is my parse class
let object = MYPFObject()
object.data = "Hello"
///now when the object is saved
object.saveInBackground { (success, error) in
//Here printing the object itself will give you the object ID
debugPrint(object)
}
Sample output would be something like
<MYPFObject: 0x1c02b2660, objectId: jE01A8upDM, localId: (null)> {
ACL = "<PFACL: 0x1c403f3e0>";
data = Hello;
}
I found this answer on the Parse documentation, including it in case it helps others...
Relational Data
Objects can have relationships with other objects. To model this behavior, any PFObject can be used as a value in other PFObjects. Internally, the Parse framework will store the referred-to object in just one place, to maintain consistency.
For example, each Comment in a blogging app might correspond to one Post. To create a new Post with a single Comment, you could write:
// Create the post
var myPost = PFObject(className:"Post")
myPost["title"] = "I'm Hungry"
myPost["content"] = "Where should we go for lunch?"
// Create the comment
var myComment = PFObject(className:"Comment")
myComment["content"] = "Let's do Sushirrito."
// Add a relation between the Post and Comment
myComment["parent"] = myPost
// This will save both myPost and myComment
myComment.saveInBackground()

Filter a realm results list with a Swift Set or array

I am trying to figure out how to delete a local stored set of Entities which are excluded from what I receive from a server api. In this case we have let's say 10 entities stored locally in realm, and the server sends me 8 entities. 6 of them corresponds to entities I already have locally, 2 are new. Locally I have 4 entities which are "outdated" because the server doesn't provide them.
Of course I could delete everything and save the new entities, but I was trying to make things more efficient.
However I don't understand, once I created a swift Set which represents the difference between the local and remote situation, how can I delete the local objects, because realm tells me i can only delete objects which belong to the realm.
I've tried working with resultset, but I can't figure out a way to filter object with a "IN" clause to which is provided a set of objects, even if I recall doing something similar in the past.
let realm = try! Realm()
try! realm.write{
let oldEvents = Array(self.events!)
var newEvents = [Event]()
for event in events{
if let e = Mapper<Event>().map(JSONObject: event){
newEvents.append(e)
}
}
//here I create a set which contains the difference between remote and local entitites, and create an Array with them
let difference = Array(Set(newEvents).subtracting(Set(oldEvents)))
//this is obviously wrong
let objToDelete = realm.objects(Event.self).filter("event in %#", difference)
realm.delete(objToDelete)
//realm.delete(difference)->this gives the error that you can delete only objects that belong to the realm
//here I would like to create or update the new events
for newEvent in newEvents{
realm.create(Event.self, value: event, update: true)
}
}
Any hint would be greatly appreciated on how to deal with this situation. Thanks!

How to avoid photos from being replaced Firebase Storage [duplicate]

With the new Firebase API you can upload files into cloud storage from client code. The examples assume the file name is known or static during upload:
// Create a root reference
var storageRef = firebase.storage().ref();
// Create a reference to 'mountains.jpg'
var mountainsRef = storageRef.child('mountains.jpg');
// Create a reference to 'images/mountains.jpg'
var mountainImagesRef = storageRef.child('images/mountains.jpg');
or
// File or Blob, assume the file is called rivers.jpg
var file = ...
// Upload the file to the path 'images/rivers.jpg'
// We can use the 'name' property on the File API to get our file name
var uploadTask = storageRef.child('images/' + file.name).put(file);
With users uploading their own files, name conflicts are going to be an issue. How can you have Firebase create a filename instead of defining it yourself? Is there something like the push() feature in the database for creating unique storage references?
Firebase Storage Product Manager here:
TL;DR: Use a UUID generator (in Android (UUID) and iOS (NSUUID) they are built in, in JS you can use something like this: Create GUID / UUID in JavaScript?), then append the file extension if you want to preserve it (split the file.name on '.' and get the last segment)
We didn't know which version of unique files developers would want (see below), since there are many, many use cases for this, so we decided to leave the choice up to developers.
images/uuid/image.png // option 1: clean name, under a UUID "folder"
image/uuid.png // option 2: unique name, same extension
images/uuid // option 3: no extension
It seems to me like this would be a reasonable thing to explain in our documentation though, so I'll file a bug internally to document it :)
This is the solution for people using dart
Generate the current date and time stamp using:-
var time = DateTime.now().millisecondsSinceEpoch.toString();
Now upload the file to the firebase storage using:-
await FirebaseStorage.instance.ref('images/$time.png').putFile(yourfile);
You can even get the downloadable url using:-
var url = await FirebaseStorage.instance.ref('images/$time.png').getDownloadURL();
First install uuid - npm i uuid
Then define the file reference like this
import { v4 as uuidv4 } from "uuid";
const fileRef = storageRef.child(
`${uuidv4()}-${Put your file or image name here}`
);
After that, upload with the file with the fileRef
fileRef.put(Your file)
In Android (Kotlin) I solved by combining the user UID with the milliseconds since 1970:
val ref = storage.reference.child("images/${auth.currentUser!!.uid}-${System.currentTimeMillis()}")
code below is combination of file structure in answer from #Mike McDonald , current date time stamp in answer from # Aman Kumar Singh , user uid in answer from #Damien : i think it provides unique id, while making the firebase storage screen more readable.
Reference ref = firebaseStorage
.ref()
.child('videos')
.child(authController.user.uid)
.child(DateTime.now().millisecondsSinceEpoch.toString());

Resources