issue with cloud firestore firebase query in flutter - firebase

I haveposts(collection) -> userid(document) -> userposts(collection) -> postid(document) -> fields (postname,description)how can i perform search based on postname i want to get post from all users
i have following and followers collection
following -> userid(document)->userfollowing (collection) -> all the user ids(documents)
followers -> userid(document)->userfollowers (collection) -> all the user ids(documents)
I have a timeline page the current logged in user should be able to see all the post from users to whom he has followed
(i am ready to change the structure it this is not correct)

I have a timeline page the current logged in user should be able to see all the post from users to whom he has followed
In this case you would have to first fetch UIDs of all users that the current user has followed i.e. read the userfollowers sub-collection documents. If there is any limit on how many users can a user follow, I'd recommend storing those UIDs in an array in user's document itself so it won't cost you N reads everytime you fetch user's followers (N is number of users that this user follows.)
(i am ready to change the structure it this is not correct)
I'd recommend creating 2 different collections to store users and posts as shown below:
users -> {userId}
(col) (doc)
posts -> {postId}
(col) (doc)
You would have to store a field userId in each post containing the author's UID
Once you have fetched the UIDs, you would have to fetch posts of each user individually. You can use whereIn operator as shown below but that can be used only if you have at most 10 UIDs in the user followings list.
FirebaseFirestore.instance
.collection('posts')
.where('userId', arrayContainsAny: [...theArrayContainingUserFollowingsID])
.get()
.then(...);
However if you have more than 10 users in following, then you would have to fetch each user's posts individually or create batches of 10 and use arrayContainAny as shown above.
You can also paginate your data to save reads and fetch data that user has requested instead of all posts from user.

Related

Flutter cloud firestore : query document->map->array

My db design is above picture. I wanna create a query which returns user where tags are matched. But i didnt any solution to query.
This is my flutter code:
But it doesnt work. How can i query array of map of document?
The courses is an array and not a map so you cannot use the dot notation to query. If the courses is made a collection (or a sub-collection) on it's own then you would be able to query users easily:
users -> {userId}
(col) (doc)
courses -> {courseId}
(col) (doc)
You would have to include a field userId in each course document which would be used to identify which user owns that course.
await firestore.collection("courses").where("tags", arrayContainsAny: tagKeys)
This will return all courses where the tags array contains at least 1 item in the tagKeys list. If you need exact match i.e. all the tags in tagKeys must be present in Firestore document then you would have to restructure the database as mentioned in this answer.
Fetching all matching documents might not be ideal since you just need user IDs that matches the tags. In that case you can store a field which contains tags from all the courses in a single array field in the user document.

Firestore chat app - build messages home page

I have a Flutter chat app with Firestore RTDB backend:
messages (collection)
message_1 (document)
chat (collection)
chat_1 (document)
chat_2 (document)
users (array, document field)
user_id_1 (String)
user_id_2 (String)
user_info (map to store user info, like name, avatar etc)
message_2 (document)
chat (collection)
chat_1 (document)
chat_2 (document)
users (array, document field)
user_id_1 (String)
user_id_2 (String)
user_info (map to store user info, like name, avatar etc)
I want to create a home page where it shows all the chats a user is involved in, sorted by most recent, just like any normal chat app:
I know how to show the chats the user is involved in. Problem is, I don't know how to handle the sorting. There is one simple way to do this: each time a new message is sent, use a cloud function and update a field in the message document called lastSent, then do orderBy('lastSent', descending: true) in your query. The problem is, each time you send a message, you have to do two writes instead of one just to update this field. Is there a better way to handle this?
Note: My app is not solely a chat app, this is only part of the main app. Imagine a chat functionality similar to Airbnb, so the volume or frequency of chat messages may not be as large as Facebook messenger for example
The common solution is to do what you propose: store a latest_updated timestamp in the document of each "chat room".
That indeed means that you'll need to do two writes instead of one. But on the other hand, you can now determine the correct ordering with by just reading the "chat room" documents, instead of having read individual messages under it.
Note that, while it is certainly possible to do this with Cloud Functions, this can also be done directly from the client. You can even catch the requirement in security rules that a client can only write a new message, if they also set the latest_updated timestamp of the corresponding "chat room" document to the same value as the timestamp of the message, although this will incur the cost of one additional document read for each message you add.

Which is a more optimal Firestore schema for getting a Social Media feed?

I'm toying with several ideas for using Firestore for a social media feed. So far, the ideas I've had haven't panned out, so for this one I'm hoping to get the community's feedback.
The idea is to allow users to post information, or to record their activity, and to any user following/subscribed to that information, display it. The posts information would be in a root collection called posts.
The approaches, as far as I can tell, require roughly the same number of reads and writes.
One idea is to have within the users/{userId} have a field called posts which is an array of documentIds that I'm interested in pulling for the user. This would allow me to pull directly from posts and get the most up-to-date version of the data.
Another approach seems more Firebasey which is to store documents within users/{userId}/feeds that are copies of the posts themselves. I can use the same postID as the data in posts. Presumably, if I need to update the data for any review, I can use a group collection query to get all collections called feeds, where the docID is equal (or just create a field to do a proper "where", "==", docId).
Third approach is all about updating the list of people who should view the posts. This seems better as long as the list of posts is shorter than the lists of followers. Instead of maintaining all posts on every follower, you're maintaining all followers on each post. For every new follower, you need to update all posts.
This list would not be a user's own posts. Instead it would be a list of all the posts to show that user.
Three challengers:
users/{userId} with field called feed - an array of doc Ids that point to the global posts. Get that feed, get all docs by ID. Every array would need to be updated for every single follower each time a user has activity.
users (coll)
-> uid (doc)
-> uid.feed: postId1, postId2, postId3, ...] (field)
posts (coll)
-> postId (doc)
Query (pseudo):
doc(users/{uid}).get(doc)
feed = doc.feed
for postId in feed:
doc(posts/{postId}).get(doc)
users/{userId}/feed which has a copy of all posts that you would want this user to see. Every activity/post would need to be added to every relevant feed list.
users (coll)
-> uid (doc)
-> feed: (coll)
-> postId1 (doc)
-> postId2
-> postId3
posts (coll)
-> postId (doc)
Query (pseudo):
collection(users/{uid}/feed).get(docs)
for post in docs:
doc(posts/{post}).get(doc)
users/{userId}/feed which has a copy of all posts that you would want this user to see. Every activity/post would need to be added to every relevant feed list.
users (coll)
-> uid (doc)
posts (coll)
-> postId (doc)
-> postId.followers_array[followerId, followerId2, ...] (field)
Query (pseudo):
collection(posts).where(followers, 'array_contains', uid).get(docs)
Reads/Writes
1. Updating the Data
For the author user of every activity, find all users following that
user. Currently, the users are stored as documents in a collection, so this is followerNumber document reads. For each of the users, update their array by prepending the postId this would be followerNumber document writes.
1. Displaying the Data/Feed
For each fetch of the feed: get array from user document (1 doc read). For each postId, call, posts/{postId}
This would be numberOfPostsCalled document reads.
2. Updating the Data
For the author user of every activity, find all users following that
user. Currently, the users are stored as documents in a collection, so this is followerNumber document reads. For each of the users, add a new document with ID postId to users/{userId}/feed this would be followerNumber document writes.
2. Displaying the Data/Feed
For each fetch of the feed: get a certain number of posts from users/{userId}/feed
This would be numberOfPostsCalled document reads.
This second approach requires me to keep all of the documents up to date in the event of an edit. So despite this approach seeming more firebase-esque, the approach of holding a postId and fetching that directly seems slightly more logical.
3. Updating the Data
For every new follower, each post authored by the person being followed needs to be updated. The new follower is appended to an array called followers.
3. Displaying the Data
For each fetch of the feed: get a certain number of posts from posts where uid == viewerUid
Nice, when I talk about what is more optimal I really need a point or a quality attribute to compare, I' will assume you care about speed (not necessary performance) and costs.
This is how I would solve the problem, it involves several collections but my goal is 1 query only.
user (col)
{
"abc": {},
"qwe": {}
}
posts (col)
{
"123": {},
"456": {}
}
users_posts (col)
{
"abc": {
"posts_ids": ["123"]
}
}
So far so good, the problem is, I need to do several queries to get all the posts information... This is where cloud functions get into the game. You can create a 4th collection where you can pre-calculate your feed
users_dashboard
{
"abc": {
posts: [
{
id: "123", /.../
}, {
id: "456", /.../
}
]
}
}
The cloud function would look like this:
/* on your front end you can manage the add or delete ids from user posts */
export const calculateDashboard = functions.firestore.document(`users_posts/{doc}).onWrite(async(change, _context) {
const firestore = admin.firestore()
const dashboardRef = firestore.collection(`users_dashboard`)
const postRef = firestore.collection(`posts`)
const user = change.after.data()
const payload = []
for (const postId of user.posts_ids) {
const data = await postRef.doc(postId).get().then((doc) => doc.exists ? doc.data() : null)
payload.push(data)
}
// Maybe you want to exponse only certain props... you can do that here
return dashboardRef.doc(user.id).set(payload)
})
The doc max size is 1 MiB (1,048,576 bytes) that is plenty of data you can store in, so you can have like a lot of posts here. Let's talk about costs; I used to think firestore was more like to have several small docs but I've found in practice it works equally well with big size into a big amount of docs.
Now on your dashboard you only need query:
const dashboard = firestore.collection(`users_dashboard`).doc(userID).get()
This a very opinionated way to solve this problem. You could avoid using the users_posts, but maybe you dont want to trigger this process for other than posts related changes.
It looks like your second approach is best in this situation.. I don't really understand what #andresmijares was trying to do and he mentioned something like storing posts in a document which is not a good approach, imagine if you have more than 20K posts (which what I think a document can hold) then the document won't be able to store any more data.. a better approach is to store posts as a document inside a Collection (just like in your 2nd option).. So let's recall here what's the best approach.
1)_ You share a post in the (posts "Collection") and in users you're following's (Feed "Collection").. maybe this can be done with cloud function and let's not forget to aggregate (with cloud functions also) the number of posts that needs to appear in the user's profile.
2)_ You follow a user and get all of their posts from the (posts "Collection") into your (Feed "Collection") this way you get to see all of their posts on your feed.
with this approach, there will be a lot of writes once but the read will be fast.. and if your app is about reading more and writing less then there's nothing to worry about unless i'm wrong.

Store and Query Posts in Firestore in a performant way

So I need to store Posts that are created by Users, now the data modell is the problem, bringing all existing Posts in a Posts Collection with a field of creatorUserID will make it able to show posts belonging to a user.
Now a User has a Subcollection called Followers with the ID of people following, the problem with that is that Im not sure how a query would look to show only Posts of People that the User follows.
Also im worried about performance when there are 10mio+ Posts in the collection.
In order to query a document in Firestore the data you want to query by needs to be on the Document you want to query, there is no way of querying a collection by the data of a document from another collection. This is why your use-case is a bit tricky. It might not seem very elegant, but this is a way of solving it:
We use two collections, users and posts.
User
- email: string
- followingUserIDs: array with docIds of users you are following
Posts
- postName: string
- postText: string
- creatorUserID: string
To find all the posts belonging to all the users the logged in user is following, we can do the following in the code:
1 Retrieve the logged in user document
2 For each id in the "followingUserIDs" array I would query Firestore for the Posts. In JavaScript it would be something like:
followingUserIDs.map(followingUserId => {
return firestore.collection('Posts', ref => ref.where('creatorUserID',
'==', followingUserId));
})
3 Combine the result from all the queries into one array of posts

Cloud function to find and delete deleted user id in every sub-collection

Q. What's the best practice to find and remove a deleted user id from different sub-collections friends under each user id in users collection?
Assumption
This is an example data structure.
- users [collection]
- user A [document]
- friends [sub-collection]
- cqeDIDZhOtf7DmGX42XPs6jwjX22 [document]
- user B
- friends
- cqeDIDZhOtf7DmGX42XPs6jwjX22
User cqeDIDZhOtf7DmGX42XPs6jwjX22 has removed from the cloud function below.
exports.deleteUser = functions.auth
.user()
.onDelete(event => {
// perform desired operations ...
});
Q. What's the most efficient way to write a cloud function which finds a match with the following user id cqeDIDZhOtf7DmGX42XPs6jwjX22 in friends sub-collection and removes from the Firestore?
You're going to have to iterate all your user documents, then delete the document in each friends subcollection with the id of the user that was deleted. There's no way to have a single query span multiple collections - you're going to have to reach into them all individually.

Resources