How do I create a new collection? - ios

I'm building a chat app where there are GroupConversations and GroupMessages. Following the example of some of the Firestore YouTube videos, I've chosen to structure my data this way:
The ID of the GroupConversation is the ID for the collection of GroupMessages associated with that GroupConversation. Then inside of that document should be a new collection called Messages which holds documents.
Does that make sense or am I overcomplicating things? I can't seem to create that Messages collection or set anything to it in my Swift code
GroupConversations
id
title
desc
memberIds
GroupMessages
GroupConversationID
createdAt: Date // just because I need a key and a document?
Messages (new collection)
Message1
Message2
Message3
Thanks
Edit: Added Swift code
let db = Firestore.firestore()
var data: [String: Any] = [
"text": title,
"senderUsername": username,
"createdAt": Date(),
"updatedAt": Date()
]
let creationData: [String: Any] = [
"text": "Group Created",
"createdAt": Date()
]
let doc = db.collection("GroupMessages").addDocument(data: creationData)
db.document("GroupMessages/\(groupConvo.documentId)/\(doc.documentID)/messages").setData(data) { error in
if error == nil {
completion()
}
}
In my console, I'm seeing a new GroupMessage object created but not sub-collection of that GroupMessage called messages

You're not building the path to the document correctly. Document paths always alternate between collection and document names. To build a path to a document in a subcollection, it would go collection/document/subcollection/document. It looks like you've mixed up the last two elements in the string you're building.
In my opinion, it's harder to go wrong if you build up the document using methods rather than trying to build a string. Something like this:
db
.collection("GroupMessages")
.document(groupConvo.documentId)
.collection("Messages")
.document(doc.documentId)
.setData(...)

Related

Swift & Firestore - appending dictionary to nested array using .arrayUnion()

I am creating a chat application where a user can start multiple chats with a different person (just like the rest of the other chat apps). I'm using Swift with Cloud Firestore.
My database design is the following:
Chat collection: [
"chatMember": ["member1", "member2"],
"createdAt" : Date().timeIntervalSince1970,
"meesages" : []
]
One chat room will have 'messages' array and it will have message dictionaries aka an object.
Below code is where I am trying to append the message dictionary to the messages array.
The Firebase doc introduces .arrayUnion() <LINK to the document>.
But it gives me an error saying,
"Contextual type '[Any]' cannot be used with dictionary literal"
#IBAction func sendBtnPressed(_ sender: UIButton) {
if let messageBody = messageField.text, let messageSender = Auth.auth().currentUser?.email {
db.collection("collectionName").document("docName").updateData([
// FieldValue.arrayUnion() does not work for this case.
K.FB.messages: FieldValue.arrayUnion([
"sender": messageSender,
"content": messageBody,
"createdAt": Date().timeIntervalSince1970
])
]) { ... closure }
}
}
I can't find any information specifically related to Swift's appending Dictionary to nested array in the Cloud Firestore.
I found a very similar case on YouTube <https://youtu.be/LKAXg2drQJQ> but this is made with Angular which uses an object with { ...curly bracket}. FieldValue.arrayUnion() seems to work on other languages but not Swift.
It'd be awesome if someone who has resolved this issue would help me out.
Thank you in advance.
So basically what you're saying with this code:
K.FB.messages: FieldValue.arrayUnion([
"sender": messageSender,
"content": messageBody,
"createdAt": Date().timeIntervalSince1970
])
is that you want to append dictionary to array with name that is in K.FB.messages constant. But you don't really want to do that and it isn't even possible. You want to append array of dictionaries to array. This means that you must enclose your dictionary in a square brackets. Like shown below:
K.FB.messages: FieldValue.arrayUnion([[
"sender": messageSender,
"content": messageBody,
"createdAt": Date().timeIntervalSince1970
]])

How to structure a search list in Firestore?

I want to show a list of artists in my app which the user will be able to search through. I'm not sure however how to save this in Firestore?
First I created a collection "searchLists" with a document for each DJ but that means a lot of document reads so that's out of the question.
Now I created a document called "artists" which has a field "artistsDictionary" which contains all the artists.
| searchLists (collection)
* artists (document)
- artistsArray (array)
0: (map)
name: "Artist 0" (string)
1: (map)
name: "Artist 1" (string)
2: (map)
name: "Artist 2" (string)
And I retrieve and parse the array as followed:
let docRef = db.collection("searchLists").document("artists")
docRef.getDocument { (document, error) in
if let document = document, document.exists {
guard let documentData = document.data() else { return }
let artistsDictionaryArray = documentData["artistsArray"] as? [[String: Any]] ?? []
let parsedArtists = artistsDictionaryArray.compactMap {
return SimpleArtist(dictionary: $0)
}
self.artistsArray = parsedArtists
} else {
print("Document does not exist")
}
}
(SimpleArtist is a struct containing a "name" field.)
And I mean, it works, but I'm still new to Firestore and this seems kinda off. Is it? Or is this how I should/could do it?
First I created a collection "searchLists" with a document for each DJ but that means a lot of document reads so that's out of the question.
This is the right approach, so you should go ahead with it.
Why do I say that?
According to the official documentation regarding modeling data in a Cloud Firestore database:
Cloud Firestore is optimized for storing large collections of small documents.
Storing data in an array is not a bad option but this is most likely used, let's say to store favorite djs. I say that because the documents have limits in Firestore. So there are some limits when it comes to how much data you can put into a document. According to the official documentation regarding usage and limits:
Maximum size for a document: 1 MiB (1,048,576 bytes)
As you can see, you are limited to 1 MiB total of data in a single document. When we are talking about storing text, you can store pretty much but as your array getts bigger, be careful about this limitation.
First off, Alexs' answer is 100% correct.
I want to add some additional data points that may help you in the long run.
The first item is arrays. Arrays are very challenging in NoSQL databases - while they provide a logical sequence data via the index, 0, 1, 2 they don't behave like an array in code - so for example; Suppose you wanted to insert an item at an index. Well - you can't (*you can but it's not just a simple 'insert' call). Also, you can't target array elements in queries which limits their usefulness. The smallest unit of change in a Firestore array field is the entire field - smaller changes to individual elements of a field can't be made. The fix is to not use arrays and to let FireStore create the documentID's for you data 'objects' on the fly e.g. the 'keys' to the node
The second issue - (which may not be an issue currently) is how the data is being handled. Suppose you release your app and a user has 2 million artists in their collection - with your code as is, all of that data is downloaded at one time which will probably not be the best UI experience but additionally, it could overwhelm the memory of the device. So working in 'chunks' of data it a lot easier on the device, and the user.
So I put together some sample code to help with that.
First a class to store your Artist data in. Just keeps track of the documentID and the artist name.
class ArtistClass {
var docId = ""
var name = ""
init(aDocId: String, aName: String) {
self.docId = aDocId
self.name = aName
}
}
and a class array to keep the artists in. This would be a potential dataSource for a tableView
var artistArray = [ArtistClass]()
This is to write an artist as a document instead of in an array. The documentID is a FireStore generated 'key' that's created for each artist.
func writeArtists() {
let artistsRef = self.db.collection("artists")
let floyd = [
"name": "Pink Floyd"
]
let zep = [
"name": "Led Zeppelin"
]
let who = [
"name": "The Who"
]
artistsRef.addDocument(data: floyd)
artistsRef.addDocument(data: zep)
artistsRef.addDocument(data: who)
}
and then function to read in all artists.
func readArtists() {
let artistsRef = self.db.collection("artists")
artistsRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let docId = document.documentID
let name = document.get("name") as! String
let artist = ArtistClass(aDocId: docId, aName: name)
self.artistArray.append(artist)
}
for a in self.artistArray { //prints the artists to console
print(a.docId, a.name)
}
}
}
}
So your data in Firestore looks like this
artists (collection)
8lok0a0ksodPSSKS
name: "Let Zeppelin"
WKkookokopkdokas
name: "The Who"
uh99jkjekkkokoks
name: "Pink Floyd"
so then the cool part. Suppose you have a tableView that shows 10 artists at a time with a down button to see the next 10. Make this change
let artistsRef = self.db.collection("artists").order(by: "name").limit(to: 10)
Oh - and you'll notice the function of sorting now goes the server instead of the device - so if there's a million artists, it's sorted on the server before being delivered to the device which will be significantly faster.
You can also then more easily perform queries for specific artist data and you won't need to be as concerned about storage as each artist is their own document instead of all artists in one.
Hope that helps!

How to store data to multiple collections with same ID in Firestore?

I have data that I want to store in multiple collections and apply the best practices of Firebase Firestore. Here's a document:
[
"postID": documentID,
"category": self.category,
"title": self.postTitle,
"description": self.postDescription,
"date": self.postDate,
"imageURL": self.postImageURL
]
My idea is to store the same document in multiple places by same ID so it's faster to retrieve. Here's what I'm trying to do:
let documentID: String = db.collection("collectionName").document().documentID
let allPosts: DocumentReference = db.collection("allPosts").document(documentID)
let postsByDate: DocumentReference = db.collection("dates").document(self.postDate).collection(documentID)
let postsByCategory: DocumentReference = db.collection("category").document.(self.category).collection(documentID)
When I have generated documentID once for all, I don't want to addDocument but rather setData so that it doesn't nest the data with another ID but CollectionReference doesn't seem to have setData.
I even tried using db.document("dates").setData() but that throws an error:
Terminating app due to uncaught exception 'FIRInvalidArgumentException', reason: 'Invalid document reference. Document references must have an even number of segments, but dates has 1'
How should I go about this?
You can use setData() with your three existing DocumentReference objects and pass it the dictionary with the data to add to the collection. You don't need to call other any methods on a CollectionReference other than what you're already doing.
let data = ... // your dictionary of data
allPosts.setData(data)

Updating child value to Firebase iOS and referencing auto ID in other nodes

I have this structure in mind to write data to Firebase server. However i am having complications in the actual code to write the structure this way.
The part i am confused on is getting the autoID (-LETR-XJvQsZCOpG-T1N) as a reference for all_images and all_plan_deatils nodes. I cannot figure out the best way to write this code out. Any suggestions would greatly help. Thank you in advance
PlanIt
plans
-LETR-XJvQsZCOpG-T1N
Uid: "ZjtJdkjzuxc0mZn4u9TfWsXa9jh2"
date: "20180609"
title: "This weekend Plans?"
-UYijs09as9jiosdijfi
Uid: "some uid"
date: "20180609"
title: "some title"
all_images:
-LETR-XJvQsZCOpG-T1N
image_0: "https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.c.."
image_1: "https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.c.."
all_plan_details
-LETR-XJvQsZCOpG-T1N
detail_0: "Bike Rodeo, Safety Presentation and Riding Tour o"
detail_1: "Bike Rodeo, Safety Presentation and Riding Tour o"
UPDATE
this is the function where i am attempting to update nodes. My thought process here is - I first set the plan title and UID to be created as an Auto Id. i then attempt to reference the "key" that was created by the auto ID, then i try to store all_images & all_dates to be stored with a reference to the same key that was created by the childByAutoID call, and then i update those values.
Sorry for any mess or confusion within the code, i am literally editing things as we speak so certain names and such may be off, but i hope you can understand the general idea of what i am trying to attempt still.
func uploadPlanitData(plan: [String], withDate: [String], withImage: [String], withTitle: String, forUID uid: String, sendComplete: #escaping (_ status: Bool) -> ()) {
_REF_PLANITS.childByAutoId().updateChildValues(["Planit Title" : withTitle, "uid": uid])
let key = _REF_PLANITS.key
let allImages = "all_images/\(key)/"
let allDates = "all_dates/\(key)"
let childUpdate = [allImages : withImage, allDates: withDate]
_REF_ALL_IMAGES.updateChildValues(childUpdate)
sendComplete(true)
}
UPDATE - that block of code currently structures my DB like this- which is ALMOST correct - except within the all_images & all_dates nodes , instead of "planits" - im expecting to have that key (-LEaaZrwYI_SqonuREV9) there. That way i have a way to uniquely group each image and date with the plan they belong to
Planit
Plans
-LEaaZrwYI_SqonuREV9
senderId
title:
all_dates
planits
0: ""
all_images
planits
0: ""
If I understand it correctly you want to create new key for a new plan, and then store information in other nodes with the same key. If that is the case, it'd look like this:
let newPlanetRef = _REF_PLANITS.childByAutoId()
newPlanetRef.updateChildValues(["Planit Title" : withTitle, "uid": uid])
let key = newPlanetRef.key
let childUpdate = [allImages : withImage, allDates: withDate]
_REF_ALL_IMAGES.child(key).updateChildValues(childUpdate)
This assumes that _REF_PLANITS is a DatabaseReference to /PlanIt/plans and _REF_ALL_IMAGES is a reference to /PlanIt/all_images.

iOS : Firebase Send/Save Data in chunks

I have trying to save data in firebase like this
class FirebaseManager {
static let shared = FirebaseManager()
private let tableRef = Database.database().reference(withPath: "XYZDemo")
func add(item: HealthData) {
self.tableRef.childByAutoId().setValue(item.dictionary)
}
}
This code save data one bye one.
How can i add more than one data at a time i.e save 5 values at once?
To simultaneously write to specific children of a node without overwriting other child nodes, use the updateChildValues method.
Basicly you create an array with the updates/writes you want to perform and write that array to Firebase. Here is an example showing 1 post being written to two different locations:
//Create the new key
let key = self.tableRef.childByAutoId().key
//Post data that is going to be written
let post = ["uid": userID,
"author": username,
"title": title,
"body": body]
//Create the array with (two) updates
let childUpdates = ["/posts/\(key)": post,
"/user-posts/\(userID)/\(key)/": post]
//Write the array to the database
ref.updateChildValues(childUpdates)
A couple things to keep in mind when you are dong this:
It's all or nothing, either all writes fail or all writes succeed. If even 1 of the writes in the array fails then all writes will fail.
Make sure you use the correct reference for each write because it will override all the data at that location.

Resources