Firestore chat app - build messages home page - firebase

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.

Related

Nested snapshot listeners

I use Google Firestore for my iOS app built in Swift/SwiftUI and would like to implement the Snapshot listeners feature to my app.
I want to list all documents in debts collection in realtime by using snapshot listeners. Every document in this collection has subcollection debtors, which I want to get in realtime for each debts document as well. Each document in debtors has field userId, which refers to DocumentID in users collection which I would also love to have realtime connection on (for example when user changes his name I would love to see it instantly in the debt entity inside the list). This means I must initialize 2 more snapshot listeners for each document in debts collection. I'm concerned that this is too many opened connections once I have like 100 debts in the list. I can't come up with no idea apart from doing just one time fetches.
Have anyone of you ever dealt with this kind of nested snapshot listeners? Do I have a reason to worry?
This is my Firestore db
Debts
document
- description
- ...
- debtors (subcollection)
- userId
- amount
- ...
Users
document
- name
- profileImage
- email
I uploaded this gist where you can see how I operate with Firestore right now.
https://gist.github.com/michalpuchmertl/6a205a66643c664c46681dc237e0fb5d
If you want to read all debtors documents anywhere in the database with a given value for userId, you can use a collection group query to do so.
In Swift that'd look like:
db.collectionGroup("debtors").whereField("userId", isEqualTo: "uidOfTheUser").getDocuments { (snapshot, error) in
// ...
}
This will read from any collection name debtors. You'll have to add the index for this yourself, and set up the proper security rules. Both of those are documented in the link I included above.

How do I implement a follower system with private accounts in Firebase?

I'm building an an app in Firebase with a user feature and I need to implement a system to allow:
A user to follow another user
A user to see a list of the users they're following
A user to set their profile as private so that some of their data is only visible to the people following them
A user to be able to send a follow request to a user with a private profile
A user with a private profile to be able to accept/reject follow requests
So far I've made a Firestore collection at the root called users. When a user signs up with Firebase Auth, a document is made in users with the following structure:
user (document)
username: stringaccountIsPrivate: boolean
userData (collection)
userData (document)
where all the data that would be hidden if the account were private is in the userData document.
I'm not sure how I could implement the system to fulfill my requirements from here so that I could use Firestore rules to only allow followers of a private account to view that account's userData. I would appreciate it if anyone could suggest an appropriate data structure and an outline of how to write rules for this.
For this kind of situation, you must maintain two sources of truth, one for the creator and one for the user. this is done with an array of strings in both that have the user_uid and any additional information concatenated.
The goal is to have an array of CSV-like values of which you can split and render within your app.
create a concat string: entry = [user.uid, user.name, user.url].join(';');
return string to object: entry.split(';');
Doing the following ensures that only a unique entry exists
db.doc("user/user_id/userData/followers").set({ followers: Firestore.FieldValue.ArrayUnion(entry)}, {merge: true});
This is only a rough example and some backend logic will be needed to scale large - but with this, you have a theoretical limit of 200k entries depending on how much data you want to store in the string.
Additional logic would involve cloud functions reading and writing when a request to follow has been created which handles a counter that creates new documents as needed and ensure's that the counter is updated to prevent overflow since Security Rules can't do any recursive logic or manipulate the request directly.

Firebase efficiently model nXn relationship

Let's say I have the following scenario:
I have multiple events, that multiple users can attend. Also, users can attend multiple events. What's the best way of storing the required information with maintaining data consistency?
Here's what I came up with and why I don't really fancy them:
collection "events" -> event document -> subcollection "users" -> user document
Problem:
Each user exists on each event, resulting in multiple documents of each user. I can't just update user information as I would need to write to every relevant event document and fetch the relevant user documents.
Really a disaster if trying to make the least reads/writes possible
E.g.:
this.afs.collection('events').get().then(res => {
res.forEach(document => {
document.ref.collection('users', ref => ref.where('name', '==', 'Will Smith')).get()
//Change name accordingly
})
})
collection "users" -> user document -> subcollection "events" -> event document
Problem:
Each event exists on each user, resulting in multiple documents of each event. (Same problem as in the first scenario, just the other way around)
collection "users" and collection "events" with each having users and events as documents subordinate to them.
There's an array attending_events which has the relevant event id's in it.
Problem:
Kind of the SQL way of sorting things. There's the need of getting each document with a seperate query using a forEach() function.
E.g.
this.afs.collection('events').doc(eventId).get().then(res => {
res.users.forEach(elem => {
this.afs.collection('users').doc(elem.name).get()
//Change name accordingly
})
})
What am I missing, is there better approaches to model the desired architecture?
When using collection "events" -> event document -> subcollection "users" -> user document
It's not so bad as you might think. This practice is called denormalization and is a common practice when it comes to Firebase. If you are new to NoSQL databases, I recommend you see this video, Denormalization is normal with the Firebase Database for a better understanding. It is for Firebase realtime database but the same rules apply in the case of Cloud Firestore.
I want to be able to change user information or event information without the need of fetching hundreds of documents.
If you think that user details will be changed very often, then you should consider storing under each user object an array of event IDs and not use a subcollection. In the same way, you should also add under each event object an array of UIDs. Your new schema should look like this:
Firestore-root
|
--- users (collection)
| |
| --- uid (document)
| |
| --- events: ["evenIdOne", "evenIdTwo", "evenIdThere"]
| |
| --- //Other user properties
|
--- events (collection)
|
--- eventId (document)
|
--- users: ["uidOne", "euidTwo", "uidThere"]
|
--- //Other event properties
Since you are holding only references, when the name of a user is changed, there is no need to update it in all user objects that exist in events subcollection. But remember that in this approach, to get for example all events a user is apart off, you should create two queries, one to get the event IDs from user document and second to get event documents based on those event IDs.
Basically it's a trade-off between using denormalization and storing data in arrays.
What's the best way of storing the required information with maintaining data consistency?
Usually, we create the database schema according to the queries we intend to perform. For more infos, I also recommend to see my answer from the following post:
What is the correct way to structure this kind of data in Firestore?

Efficient way to check for existing related documents in NoSQL (Firestore)?

Following Current Datamodell
User
User ID
Video
VideoID
LikedBy (Subcol)
User ID
User ID
User ID
Now if a User visits a video I wanna show if he Liked the Video already or not (similar to youtubes button color if you liked already).
My current approach is querieing for a Document with the Key of the signed In UserID and if I find one it means the user liked the video. The problem is I have this for Artists that you can subscribe too similar to channels on youtube.
This alone created about 3x the initial Reads I have on Page Load.
I would like to hear if there is any more efficient way to query for such a thing or structure the data.
Be aware that if you suggest me to store all liked Shows in the User or Show Document that this is not scalable due to the 1MB Limit.
1) You can have a subcollection on the Users, storing the ids of the posts the likes.
2) You can create a users_likes, collections where the Ids is the user id and inside have an array with the ids of the posts the user likes.
3) Last, just make props called likes on the user collection an store the ids of the posts.
All options have a trade-off, I would make like a user and posts_likes query on load and keep that in memory (no external user is going to affect this).
Be aware that if you suggest me to store all liked Shows in the User or Show Document that this is not scalable due to the 1MB Limit.
If you are expecting a user to like more than 1 millions of posts... otherwise, storing 1Mb of only ids is a good idea... I use this same pattern for a user events tracking, I have events defined (equivalent to your posts) and the user make actions that correlate to those events (your likes), I have cases with more than 80K and it works like charm. I gave your 3 options, I would say, start with 3 until it doesnt work, then go to 2 and same process up to 1. Since you will work with array of ids, support yourself with this
My current approach is querieing for a Document with the Key of the signed In UserID and if I find one it means the user liked the video.
Yes, that's a correct approach.
This alone created about 3x the initial Reads I have on Page Load.
I don't know where this is coming from but there is certainly something wrong. Unfortunately, nothing in your question can help me see the problem.
I would like to hear if there is any more efficient way to query for such a thing or structure the data.
I don't understand much from your schema, but I would structure the database this way:
Firestore-root
|
--- users (collection)
| |
| --- uid (document)
| |
| --- //user properties
|
--- video (collection)
|
--- videoId (document)
|
--- likedBy: ["uid", "uid", "uid"]
As you can see, likedBy property is of type array. So once you get a video document, you can simply check the uid of the logged in user against the likedBy array. If it exists, it means that user has already liked that video, otherwise has not.

How to get field ref data in firebase in single call?

I have below firestore collections.
-Converstions(collection)
(document) {participants: {userid1: true, userid2: true}, messages: [subcollection]}
-Users(collection)
(document)(userid1){userName: 'Test1', ...}
(document)(userid2){userName: 'Test2', ...}
Now I need to query for conversations a users is in, I can do this with
firebase.firestore().collection('conversations')
.where(`participants.${uid}`, '==', true);
What this does is gets all conversation a users is participating in, I need to now get the user details from id for each document in those conversation. If we make another call to UserRef to get the user details it will make extra request for each conversation data. I wanted to know if there is easy way to get user details in single call to the firebase.
When a user is added to a document, you could also add some display information about that user (either from the app or Cloud Functions).
There is no way to return data referenced elsewhere. You either need to duplicate or make multiple fetches.

Resources