Firebase Documents with Sub-Collections in Swift using Codable - swift

I want to create a simple SwiftUI app that uses FireStore and has a collection "recipes" and a collection "users" for user specific data. In the users collection, I want to add a document for every user that holds a sub-collection with the favourite recipes of the user.
On the client side, I am using something like this for the user data:
import FirebaseFirestoreSwift
import Foundation
struct Account: Codable {
#DocumentID var id: String?
var favoriteRecipes = Set<Recipe>()
}
Now if I write the document to firebase using the Codable support, it creates a Map for the set of recipes (which is fine I guess, I just want to have it another way).
So I obviously can handle the sub-collection "manually" and use it like any other stand-alone collection.
Nevertheless, I am wondering if there is some sort of "Best-Practice" for handling sub-collections in Firebase with Codable in Swift?
Thanks a lot!

There are two parts to your question. Let me try to answer them individually.
How to handle nested data structures in Firestore / Codable?
Any attribute on a Codable struct will be mapped against the respective attribute on a Firestore document (you have some influence over thus by using the CodingKeys enum - see this article.
Nested types will be mapped to dictionaries on the document, whereas arrays and other sequences will be mapped to arrays on the document.
In order to retrieve a sub-collection of a document, you will need to perform a separate fetch request. Firestore doesn't support fetching a nested tree of documents/sub-collections on the client. It's a different story on the server, though. Check out Renaud's article to learn more about this.
How to store user-specific data?
For any user=specific data, I would recommend one of the following two ways to structure your data:
Storing as a sub-collection
In this scenario, we have one top-level collection users, which contains documents for all your users (let Firestore auto-generate the document IDs for you, and store Firebase Auth's user ID as an attribute on the respective user document.
/(root)
+ users <-- (collection)
+ 0FABQ...RiGg <-- (user document)
- uid: "6ZPt...BLiK3fnl2" <-- (Firebase Auth user ID)
- name: "Johnny Appleseed" <-- (attribute)
+ recipes (collection) <-- (sub-collection)
+ A69EF...4EFA <-- (recipe document)
- name: "Scones" <-- (attribute)
+ FCED...12D5 <-- (another user document)
You can then use the user's ID (from Firebase Auth) to query all the user's recipes.
Storing as a top-level collection
In this scenario, we have two top-level collections: one for all your users, and another one for all the recipes. In order to distinguish a user's recipes, each recipe doc has a uid attribute which contains the respective user's user ID:
/(root)
+ users <-- (collection)
+ 0FABQ...RiGg <-- (user document)
- uid: "6ZPt...BLiK3fnl2" <-- (Firebase Auth user ID)
- name: "Johnny Appleseed" <-- (attribute)
+ FCED...12D5 <-- (another user document)
+ recipes (collection) <-- (collection)
+ A69EF...4EFA <-- (recipe document)
- uid: "6ZPt...BLiK3fnl2" <-- (Firebase Auth user ID)
- name: "Scones" <-- (attribute)
To retrieve a user's recipes, you query all recipes and filter for the ones that match the user ID of the currently signed in user.
To learn more about Firestore data modelling, check out the documentation, which also contains links to a number of really useful videos. Fireship also has a really good article about this.

Related

How correctly aggregate and lookup mongo data to models

Consider two collections:
users (can be both, organizer and participant in other meetings)
meetings
For the sake of simplicity, I show here only the basic data, in my code i have emails, passwords, etc.
User (easier part)
{ "_id": "ObjectId('user0_id')", "username": "Paul" }
and model:
type User struct {
Id primitive.ObjectID `bson:"_id" json:"id,omitempty"`
Username string `json:"username"`
}
Meeting:
{
"_id": "ObjectId('meeting0_id')",
"organizer": "ObjectId('user0_id')",
"participants": [ "ObjectId('user1_id')", "ObjectId('user2_id')", "ObjectId('user3_id')"]
}
and model:
type Meeting struct {
Id primitive.ObjectID `bson:"_id" json:"id,omitempty"`
Organizer primitive.ObjectID `json:"organizer"`
Participants []primitive.ObjectID `json:"participants,omitempty"
}
Everything works if I extract only the basic data from mongo, but it seems ineffective... Because if I want to present this data in a readable form, I can't use only ObjectID, but with mongo "$lookup" I would like to get more data about users right away.
Another problem, in some cases I need a different dataset. Once to show a list I need only
the name of the users assigned to the meeting.
However, in the case of data administration or sending notifications, I need more (all?) User data.
How to correctly (what are the best practices) to store data like this in Go models?
Create one super-struct "meeting" with all possible data? Eg.with Participants []User instead of ID's ?
But what next? Get a complete set of data from the database each time, then filter it on the code side? Or filter on mongo side, but in most cases almost all fields in struct will be empty (eg. LastPasswordChangeDate in simple meeting participants list). Especially since there may be more "lookup" data, e.g. meeting place, invitations, etc., etc.
How finally save this super struct to two collections?
P.S. Create different models for different "views" of meetings seems super stupid...

Firestore - order documents by document field in subcollection

I have a Flutter app where users can rent items from each other with Firestore RTDB. rental documents have a chat collection that stores the chats between two users:
rentals (collection)
rental_1 (document)
chat (collection)
message_timestamp_1 (document)
message_timestamp_2 (document)
users (array, document field)
user_id_1 (String)
user_id_2 (String)
rental_2 (document)
chat (collection)
message_timestamp_1 (document)
etc.
I have a page in my app that is a listview of all the rentals that the user is involved in (simple arrayContains on the users field). Basically, I want to show all the chats the user is involved in. However, I would like to order this list by most recent chat (like any normal messaging app). Is there a way to achieve this without having to store and update a lastUpdated field in the rental document (thus creating two writes each time a message is sent)?
Adding that lastUpdated field in the rental document and querying the latest rental documents in which a user is involved using array-contains and ordering by lastUpdated field will solve your problem. As you have mentioned this will cost you two writes per message and can lead to a billing trap as a lot of messages can be expected in the chats sub-collection.
Alternatively you can create the chats collection as a top-level collection with a field rentalId so that you can query on this top-level collection to show the recent rental chats the user is involved in. This will eliminate the two writes you have to perform when writing a single message to the firestore.
Hope that helps.

How can I segregate user data in firebase?

I'm creating an app that has uses a firebase Cloud Firestore database. The structure seems to be collection/document/fields. I am thinking of either using the user id as the prefix to the collection name or simply a field for userId.
I'm currently using:
Firestore.firestore().collection("Events").
This could be changed to prefix event with the userId
I am currently reading everything in using:
reference(to: "Events").addSnapshotListener{ (snapshot, _) in
guard let snapshot = snapshot else {return}
for document in snapshot.documents {
// code here
}
}
It's not a really good idea to use prefixes on collection names. That doesn't work well with security rules.
The usual structure for per-user data is to have a collection with documents whose IDs are the user IDs. Then, you can further organize other data in subcollections under that document ID.
/users
/uid1
/uid2
/events
/likes
/history
Then, you can write security rules using the user's UID very easily.

Firebase Query Under AutoID Under Child Swift

Hello! I'm trying to make two Firebase queries:
All children under basicInfo, for multiple users at once (query limited by 20 results). For example, in this query with the above database I will get all basicInfo sections for -Kwz1eoUMPym... and also for -Kwzhj4pzBJxbb...
All children under basic info where name == (some name).
I can't understand how to approach a name query under the AutoID key and also under basicInfo key, for both queries.
What is the most logical approach to this?
The Firebase Realtime Database is a NoSQL database, and it's only possible to filter by direct descendents of children in a list. Therefore, you will need to use a flatter data structure, and avoid splitting your fields into basicInfo, fullInfo:
users
|- userId
|- name: "John"
|- age: 17
|- gender: "m"
|- birthday: "10.10.2000"
...
With this approach, you can attach a listener to the users node and then filter by child values using the queryOrderedByChild and queryEqualToValue methods. For example, something like this in Swift:
Database.database().reference().child("users").queryOrderedByChild("name").queryEqualToValue("(some name)").observeSingleEventOfType(.Value, with: { snap in
// Do your thing with snap
})
With either of your structures, you can limit your results with the queryLimitedToFirst and queryLimitedToLast methods. For example, from the filtering data section of the documentation:
The following example demonstrates how example blogging app retrieves a list of the 100 most recent posts by all users:
// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
let recentPostsQuery = (ref?.child("posts").queryLimited(toFirst: 100))!
For more details on filtering and limiting Firebase data in Swift/iOS, see the working with lists of data on iOS documentation.

how to retrieve all data from a particular document in couch db?

I have a document where i am saving all the user registration information with a particular random id associated to it.
my document looks like:
_id:xyz
_rev:9-a4d476aa9f4b41879d21a7f475a2f1d5
-742566755
course:gg
passwdt:t
phone:1313
clgnme:hfhjf
address:fjhfj
-884381686
course:gg
passwdt:t
phone:1313
clgnme:hfhjf
address:fjhfj
Now, i Want to retrieve the document with all information as shown in the document.
I want that it should be shown individually as i have done with getting the values on console. but i want it acording to each id individually.
Like if i want to get data of -884381686 id and it can be anything name, address anything. it should fetch data accordingly.
**NOTE:**PROGRAMMING TO BE DONE IN GWT
You mean you want to access each document via its ID? You can just make an HTTP GET request to (assuming CouchDb is run locally):
GET localhost:5984/<database>/<id>
That will return the entire document
Based on your comment below, the best way to get each item in the document is to create a map view in Couch. Say your document looks like this:
{
_id: 123,
rev: 22,
posts: [{ id: 2, name: hello}, {id: 3, name: world}]
}
If you wanted to list all of the posts, your Map view would look like this:
function(doc){
for(var post in doc.posts){
emit([doc._id, post], 1);
}
}
That will emit all of the posts in every single document. If you have different types of documents, you can add code to only emit for certain types of documents. Then you can go to the URL for the view to see all of the posts: http://localhost:5984/database/_design/ViewCategory/_view/ViewName?reduce=false