I am trying to develop a system with 2 different views, being the first "My books" and the second "Other people books"
for the first view i have this working fine
this.mybooks = angFire.database.list('/user-books', {
query: {
orderByChild: 'uid',
equalTo: firebase.auth().currentUser.uid
}
});
but i dont know how to create something that would work as a "notEqualTo" function to exclude all books owned by the current user.
this is my Firebase structure for reference
{
"user-books" : {
"-KfJ9CprqgOWN9Ud_CvG" : {
"author" : "sdasd",
"city" : "asd",
"description" : "asdasd",
"title" : "asda",
"uid" : "bazvEYBL6sgfa6HSmqvtAlX3f0l2"
},
"-KfJARoEU_FDW80hg4ws" : {
"author" : "in",
"city" : "chaaat",
"description" : "the",
"title" : "Pogchamps",
"uid" : "tzROGF1Tk4NcobrTE70ZKQzoKom1"
}
}
}
The firebase data structure you are currently using, which nests books inside user objects, is probably not the best for your exact use. The firebase docs have a section devoted to that right here. One of the subsections is titled "avoid nesting data".
A better structure would normalise books and put them in a separate collection/table. For storing the users books, you can still have the books array, but in it just store the keys (ids) from the books table.
Listing other people's books, then, just becomes listing all the books from the books table and filtering out the current user's books.
Note that, especially in data stores like firebase, the optimal data structure entirely depends on how you use the data. Storing the user-book link inside the user object is a good approach, only if your app focuses on listing a user's books. If, on the contrary, the focus would be to list users for a book, it would be better to store arrays of user ids inside the books collection.
Related
I can't manage to determine what is the better way of organizing my database for my app :
My users can create items identified by a unique ID.
The queries I need :
- Query 1: Get all the items created by a user
- Query 2 : From the UID of an item, get its creator
My database is organized as following :
Users database
user1 : {
item1_uid,
item2_uid
},
user2 : {
item3_uid
}
Items database
item1_uid : {
title,
description
},
item2_uid : {
title,
description
},
item3_uid : {
title,
description
}
For the query 2, its quite simple but for the query 2, I need to parse all the users database and list all the items Id to see if there is the one I am looking for. It works right now but I'm afraid that it will slow the request time as the database grows.
Should I add in the items data a row with the user id ? If yes the query will be simpler but I heard that I am not supposed to have twice the same data in the database because it can lead to conflicts when adding or removing items.
Should I add in the items data a row with the user id ?
Yes, this is a very common approach in the NoSQL world and is called denormalization. Denormalization is described, in this "famous" post about NoSQL data modeling, as "copying of the same data into multiple documents in order to simplify/optimize query processing or to fit the user’s data into a particular data model". In other words, the main driver of your data model design is the queries you plan to execute.
More concretely you could have an extra field in your item documents, which contain the ID of the creator. You could even have another one with, e.g., the name of the creator: This way, in one query, you can display the items and their creators.
Now, for maintaining these different documents in sync (for example, if you change the name of one user, you want it to be updated in the corresponding items), you can either use a Batched Write to modify several documents in one atomic operation, or rely on one or more Cloud Functions that would detect the changes of the user documents and reflect them in the item documents.
I am trying to create an social app using flutter, in that app I have users and their posts, so in firestore I am creating 2 collections,
Users
Posts
So, in users it will have user data like email, display picture, bio etc. For identification I'm creating a key in posts which will have a reference to the user to whom the post belongs, like so,
Now, I while I ask a particular post I also want some of the user details like UserName and display picture so that I can show it in the Post in the UI like,
So, I want to use StreamBuilder as it will reflect any changes made, but I can't get user details if I'm using StreamBuilder.
How can I achieve this?
According to your data model, you need to make two queries, the first one to get the user ID and others to get the posts corresponding to the user.
final StreamController<List<Post>> _postsController =
StreamController<List<Post>>.broadcast();
Stream listenToPostsRealTime() {
// Register the handler for when the posts data changes
_postsCollectionReference.snapshots().listen((postsSnapshot) {
if (postsSnapshot.documents.isNotEmpty) {
var posts = postsSnapshot.documents
.map((snapshot) => Post.fromMap(snapshot.data, snapshot.documentID))
.where((mappedItem) => mappedItem.userId == FirebaseAuth.instance.currentUser().uid)
.toList();
// Add the posts onto the controller
_postsController.add(posts);
}
});
return _postsController.stream;
}
I would like to recommend you to visit this video: "How to structure your data" and this post, remember that you will be charged for every read to the database, so if you're going to gather information from the Users document every time you retrieve a Post, it would be a better idea to have duplicated information from the Users document inside your Posts document, this way you only have to query your Posts document. Remember that denormalizing data (like replicating it) on the NoSQL world is a common behaviour.
Your structure could be something similar to:
Posts:
createdAt: "June 25, 2020"
description: "New food"
email: "test#email.com"
image: "https://example.com/assets/image.jpg"
likes:
[updatedAt: "June 25,2020", user: likerUser]
user:
[ name: posterUser,
profile_picture: "https://example.com/assets/posterPicture.jpg",
profile_url: "https://example.com/profiles/userPoster"]
In order to keep your data synced between both documents I recommend you to check this answer
Summary
How could I model my database in Firebase to keep, for example, reviews in a specific page updated with the users info, this is, if a user changes it's avatar or name, the reviews should also display the updated data of the user.
I've used MongoDB most of the time, with Mongoose, and I am now working on a mobile app with Firebase. In Mongo I would just store a ref to the user in the review, and populate the field to retrieve the data I wanted from the document. Is there something like this in Firebase, and is it even a good or acceptable practice?
Quick Questions
Is there something like ".populate()" in Firebase?
Should I model the documents as much as possible to have the data that will be used in the view, and avoid "joins"?
Example
We have a users collection, and a store collection with reviews in it.
As far as I've read, you should minimize the doc reads, and so we should model our data with the specific values we need for the view were they will be used, so that we only need to do one query.
For the sake of simplification, let's say:
User has a name, email, avatar
users: {
user_id_1: {
email: "user1#gmail.com",
name: "John Doe",
avatar: "some_firestore_url"
}
}
Should the store collection:
Have nested collection of reviews like this
stores: {
store_id_1: {
name: "Dat Cool Store!",
reviews: {
user_id_1: {
name: "John Doe",
avatar: "some_firestore_url",
text: "Great store love it!",
timestamp: "May 07, 2020 at 03:30"
}
}
}
}
The problem I see with this, is that unless we use a function that updates every field in every document with the new values there is no other way to update the data in name and avatar.
Have the user_id in a field and query for the user information after:
stores: {
store_id_1: {
name: "Dat Cool Store!",
reviews: {
review_id_1: {
user: "user_id_1",
text: "Great store love it!",
timestamp: "May 07, 2020 at 03:30"
}
}
}
}
This is the mimicking the way I would do in MongoDB.
Sorry if some of it sounds confusing or I didn't explain myself the best way, but it's 4 o'clock in the morning here and I'm just trying to get it right :)
How could I model my database in Firebase to keep, for example, reviews in a specific page updated with the user's info, this is, if a user changes its avatar or name, the reviews should also display the updated data of the user.
Without knowing the queries you intend to perform, it's hard to provide a viable schema. We are usually structuring a Firestore database according to the queries that we want to perform.
In Mongo I would just store a ref to the user in the review, and populate the field to retrieve the data I wanted from the document. Is there something like this in Firebase, and is it even a good or acceptable practice?
Yes, there is. According to the official documentation regarding Firestore supported data-types, a DocumentReference is one of them, meaning that you can store only a path to a document and not the entire document. In the NoSQL world, it's quite common to duplicate data, so to have the same data in more than one place. Again, without knowing the use-case of your app it's hard to say whether using normalization it's better than holding only a reference. For a better understanding, I recommend you read my answer from the following post:
What is denormalization in Firebase Cloud Firestore?
And to answer your questions:
Is there something like ".populate()" in Firebase?
If you only store a DocumentReference, it doesn't mean that the data of the document that the reference is pointing to will be auto-populated. No, you first need to get the reference from the document, and right after that, based on that reference, you have to perform another database call, to actually get the data from the referenced document.
Should I model the documents as much as possible to have the data that will be used in the view, and avoid "joins"?
Yes, you should only store the data that you actually need to be displayed in your views. Regarding a JOIN clause, there isn't something like this supported in Firestore. A query can only get documents in a single collection at a time. If you want to get, for example, data from two collections, you'll have at least two queries to perform.
Another solution would be to add a third collection with data already merged from both collections so you can perform a single query. This is already explained in the link above.
Some other information that might be useful is explained in my answer from the following post:
Efficiency of searching using whereArrayContains
Where you can find the best practice to save data into a document, collection, or subcollection.
For me, the way I would go ahead with structuring my json collection also depends on the size of data, I am trying to store in the collection.
Let's say the number of users if small and I only want to support a thousand users. So in that case, I can go with this structure.
{
"store_id_1": {
"name": "Dat Cool Store!",
"reviews": [
{
"user_id_1": {
"name": "John Doe",
"avatar": "some_firestore_url"
},
"text": "Great store love it!",
"timestamp": "May 07, 2020 at 03:30"
},
{
"user_id_2": {
"name": "John Doe 2",
"avatar": "some_firestore_url 2"
},
"text": "Great store love it! TWO",
"timestamp": "May 27, 2020 at 03:30"
}
]
}
}
So now, you can have all the user info embedded in the stores collection. This will reduce your reads too.
But in case you want to scale it, then, I would suggest only store the users metadata and then make another read from users collection.
Hope this helps!
I'm working on a project where Firebase Firestore will be used. I've not had experience with noSQL so I work on understanding this technology.
The application lets an user to select a movie category and download an associated movie to let him movie edition.
The category and movie won't be change by the users and will be fixed or change by the owner. Think this like a Netflix catalog where the user can only watches the movies
There are several categories but for the moment only one movie is inside a category (may have more later).
Each movie has related metadata.
In the future:
- an user object will be used to rank each user regarding their score (information related to the application).
- some movie will be available with localisation restriction, i.e a movie would be available for US user only.
In my first thought, the data structure will look like this:
// Collection
Category: {
name: "drama" // Could be action, or other
}
// Collection
Movie: {
name: "Matrix"
description: "Best movie ever"
duration: 1321312
url: "https://www....."
allowedCountry: "us" // is it the right place for this field?
category: "drama" // is it ok to duplicate data here?
}
//Collection
user: {
ranking: 3
withMovie: "Matrix" // is it ok to duplicate data here?
}
I'm not sure if it's the right data structure for this problem.
The flow of the app would present in the first time all the possible categories (so I create a separated collection in order to avoid to iterate on all the song to get the categories)
Then when the user select a category the possible movies are display and the app download the selected one.
Is is okay to iterate on all possible movies to show the movies related to the category?
Or should I put the movies as a sub collection of the category collection?
I'd typically indeed keep the category directly in the movie document in this scenario, since it makes the queries much easier to read.
firebase.firestore().collection("movies").where("category", "==", "drama")
In fact, consider if your movies can really only have a single category (as you've modeled it now), or whether they may have multiple categories in the future (as Netflix does as far as I know). You'd model the latter as an array of categories:
categories: ["drama", "sci-fi"]
And then query it with:
firebase.firestore().collection("movies").where("categories", "array-contains", "drama")
In firestore i have a collection called things.
Each thing is owned by a user.
Each thing can be shared by the owner with other specified users.
the structure of thing looks something like
{
id: "thing01",
sharedWith: {
"user1": true,
"user2": true,
},
dtCreated: 3458973948
}
When I want to retrieve all thing objects that are shared with user1, ordered by dtCreated desc,
i can't do this without having to create an index on things.thing.user1
i.e. for every unique userid i have to create an index on the things collection.
Obviously this is not practical. The docs talk about using full text search for this, but this doesn't seem like a problem we would want to use full text search for.
Is there a different way i should be structuring the data to achieve what i want?
Is firestore just the wrong technology choice for this?
It's working very well for storing the thing objects themselves.
---- update ----
this question is not a real duplicate of Firestore: Working with nested single queries because the answer provided there is very specific to the OP's context.