How to listen for changes(or get stream) from nested sub collections? - firebase

I am building a private chat feature for my app. I have chosen Firestore as my back end and below is how chat threads and messages are structured.
privateChats - chatId1 - chatId1 - messageId1, messageId2,...
collection - document - collection - documents
here is the screenshot
What I am trying to achieve is to get snapshots of every chat threads(chatId1, chatId2,...) with their corresponding message(last document/message was sent) and display like below - refer Chat screen
I have tried something like that
databaseRef.collection('privateChats').where('chatId', whereIn: chatList).snapshots();
However streams which are returned from above snapshots only listen for changes on fields where createdAt field exists.
Which gives me an only option to get individual snapshot for each chat threads like that
databaseRef.collection('privateChats').doc('chatId1).collection('chatId1).snapshots();
databaseRef.collection('privateChats').doc('chatId2).collection('chatId2).snapshots(); and somehow combine these snapshots into single stream (I don't know how)
So my question is - is there a way to get snapshots of each chat threads with just a single query when given list of chat ids? If not, what could be the solution? Should I come up with different cloud firestore structure?
Thanks

what i think you can do is create a doc for each chatRoom ..like say
databaseRef.collection('privateChats').doc('chatId1+chatId2')
then both users id1 and id2 can only access the document

I solved my problem using rxdart MergeStream function as emir adejuwon suggested

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 to wrap my head around a FireStore query

I am new in Flutter - Firestore
I am learning flutter with firebase and creating a sample dating app
I have a list of users that I get in a stream and display it using List view
Firestore.instance.collection('users').snapshots()
I have learnt to filter this like so
.where((user) => user.age < settings.agemax && user.age > settings.agemin))
and all this works.
I also have a subcollection called shortlist (list of users that current user has shortlisted) that I get using,
Firestore.instance.collection('users').document(uid).collection('shortlist').snapshots()
Now I am trying to redefine my first query GetUsers with filters based on following
How do I exclude shortlisted users that I am fetching in a stream from all users stream
Similarly would also need to filter out "matched users" and "Blocked / declined" users as well !
I believe my question is how do I query Users Collection and exclude records with uid's that contained in a Shortlist subcollection. I am planning to use the same logic for matches and blocked !? am I on the right track ?
also ... do I need to refetch all records when a users shortlists/matches/blocks someone, as the stream would change or is there a way to remove that one record from the listview without rebuilding, may be I should separate this question in two.
If I understand correctly you are looking for the (just introduced) not-in operator, so I recommend also checking out this blog post.
I expect that this operator hasn't landed in the Flutter libraries yet, as that may take some time. I recommend checking the upcoming releases to see when it lands, or checking/filing an issue on the repo.
Until then, there's no way to exclude results from a query, so you will have to exclude the items from the stream of results in your application code.

Flutter Profile Matching App - Cloud Firestore Data Modeling | How to Query to avoid unnecessary reads for already swiped UID´s

I tried my best with the search function and not really getting the results I´m searching for.
At the moment I try to replicate a functionality for swiping profiles on a card stack. The profiles are loaded over a Firebase Firestore backend. The problem is, due to the geoFireStore query, I get with every load within a specific radius a whole bunch of documents and I would like to reduce the read amount directly in the query.
So what I'm trying to achieve is, that when I swiped a profile once (left or right) I never want to read it in the query again. In addition, to save reads, I don't want to do this client sided, it should be done in the query directly.
Firestore JSON structure at the moment:
Users (collection)
UIDs [documents]
name
active
match {map}
If the match map doesn't contain the own UID (if exist is null) then read in query, else when own UID exist in other user profile under the map match (boolean: true or false for liked or disliked) then don't show as query result.
My stream is built this way:
return geo
.collection(
collectionRef:
friendrCollection.where("active", isEqualTo: true).where("match.$uid", isNull: true))
.within(
center: geoFirePoint,
radius: suchRadius,
field: 'position',
strictMode: false)
.map(_friendrsGPSListFromSnapshot);
}
I am working on this for 3 weeks now and not getting the results I want :D
-Should I stay with firestore or better the real-time-database?
-How do I need to structure the data to get a "except" query working?
Right now it is like:
Collection: Users --> Documents: UIDs --> Match{ownUID}
Thanks in advance
Andreas :)
I still not managed to find a proper solution to reduce the number of unnecessary profile reads inside the firestore query.
Has anyone an idea to handle this part?
The only way I found was to lookup existing matches on the client side (extra firestore collection), but that means that I always have to load the whole bundle of profiles and throwing them away afterward :(
Thank you!
Andreas

How to sort chat channels for Chat app using Cloud firestore

I am designing a chat app using cloud firestore and the schema looks something like this :
Each 1:1 chat is created inside a chats collection. Each chats collection will have a set of fields and a sub collection. Each sub collection will have all the messages. It looks something like this -
/chats
* id (auto gen)
* channel ( string type - sort(uid1 , uid2) )
* uid1
* uid2
* createdTime
* lastMessageSentTime
* messages (sub collection)
* fromUid
* toUid
* content
* sendTime
To show the initial chat screen, with all users the user has been chatting with, I query the /chats collection and sort the results by lastMessageSentTime.
One problem I see with this design is that I need to update the lastMessageSentTime for every message sent or received. Is there a way I can avoid this double writing ?
Can I use the messages sub collection's sendTime to somehow show the first chat screen or is there another way out ?
Thanks in advance !
There is no way to query the top-level chats collection based on a value in the messages subcollection.
So you have two options:
Write the lastMessageSentTime into the chat room document, and update it every time you write a message for that chat room. That's your current proposal.
Determine the correct order on reading the data. So you first load all chat rooms, and then load the most recent message for each chat room.
Developers who are new to NoSQL databases often start from the second option, while more experienced NoSQL developers will typically pick the first as it leads to a more simple and scalable read operation.
If you want to sort documents in a collection with one query, you can only use the documents in that one collection. You can't "join" other documents from other collections to change the results of the query. So, the way you have it now, you have to "double write" the timestamp. Duplicating data like this is very common in NoSQL type databases.

Firestore, fetch only those documents from a collection which are not present in client cache

I am implementing a one-to-one chat app using firestore in which there is a collection named chat such that each document of a collection is a different thread.
When the user opens the app, the screen should display all threads/conversations of that user including those which have new messages (just like in whatsapp). Obviously one method is to fetch all documents from the chat collection which are associated with this user.
However it seems a very costly operation, as the user might have only few updated threads (threads with new messages), but I have to fetch all the threads.
Is there an optimized and less costly method of doing the same where only those threads are fetched which have new messages or more precisely threads which are not present in the user's device cache (either newly created or modified threads).
Each document in the chat collection have these fields:
senderID: (id of the user who have initiated the thread/conversation)
receiverID: (id of the other user in the conversation)
messages: [],
lastMsgTime: (timestamp of last message in this thread)
Currently to load all threads of a certain user, I am applying the following query:
const userID = firebase.auth().currentUser.uid
firebase.firestore().collection('chat').where('senderId', '==', userID)
firebase.firestore().collection('chat').where('receiverId', '==', userID)
and finally I am merging the docs returned by these two queries in an array to render in a flatlist.
In order to know whether a specific thread/document has been updated, the server will have to read that document, which is the charged operation that you're trying to avoid.
The only common way around this is to have the client track when it was last online, and then do a query for documents that were modified since that time. But if you want to show both existing and new documents, this would have to be a separate query, which means that it'd end up in a separate area of the cache. So in that case you'll have to set up your own offline storage on top of Firestore's, which is more work than I'm typically willing to do.

Resources